Search code examples
typescriptreact-hooksreact-context

Updating object in global state array typescript react


I have been trying some days to figure out how to update my global state array in a gis-project. I use Mapbox-gl with ts and react. I use the useContext hook to keep track of a list of "GeoJSONItems", which includes a geoJSON object and some metadata. However, several ts problems occur when I try to update the global array.your text

This is my GeoJSONContext.tsx

import { createContext, useContext, useState } from "react";
import { FeatureCollection } from "geojson";

export type GeoJSONItem = {
  id: string,
  name: string,
  visable: boolean,
  color: string,
  geoJSON: FeatureCollection;
};

type GeoJSONContextType = {
  geoJSONList: Array<GeoJSONItem>;
  setGeoJSONList: (selected: GeoJSONItem[]) => void;
  setVisable: (value: boolean) => void;
};

const GeoJSONContext = createContext<GeoJSONContextType>({
  geoJSONList: [],
  setGeoJSONList: () => {},
  setVisable: () => {}
});

export const useGeoJSONContext = () => useContext(GeoJSONContext);

type Props = {
  children?: React.ReactNode;
};

const GeoJSONProvider: React.FC<Props> = ({ children }) => {
  const [geoJSONList, setGeoJSONList] = useState<GeoJSONItem[]>([]);
  const [visable, setVisable] = useState<boolean>(); 


  return (
    <GeoJSONContext.Provider value={{ geoJSONList, setGeoJSONList, setVisable }}>
      {children}
    </GeoJSONContext.Provider>
  );
};

export default GeoJSONProvider;

This is how global states should be handled with useContext (Works for local arrays) and how I try in my components:

setGeoJSONs((prevGeoJSONs) => [...prevGeoJSONs, json as FeatureCollection]);

I have managed to work it out with Array.push(), but I know its not a good practice working with global states.

So, when calling the global setGeoJSONList, when adding an Object or updating it:

setGeoJSONList((prevGeoJSONs) => [...prevGeoJSONs, json as FeatureCollection]);

ts sends this error: Argument of type '(prevGeoJSONs: any) => any[]' is not assignable to parameter of type 'GeoJSONItem[]' As if it dosen't understand that prevGeoJSONs is an array of GEOJSONItem. Note:

  • I have tried casting it as GeoJSONItem[] with ts syntax and with "as GeoJSONItem[]"
  • It works for adding one object but it requires sometimes that I have to add several and then the list would be overwritten.

Hope somebody have time to help me 😃


Solution

  • You've declared setGeoJSONList as type (selected: GeoJSONItem[]) => void - in other words, it only takes an actual array of items.

    However, you're trying to use it by passing it a function that produces an array of items:

    setGeoJSONList((prevGeoJSONs) => [...prevGeoJSONs, json as FeatureCollection]);
    

    The setter that's returned by useState<GeoJSONItem[]> can take either a value or a function that produces a value; in other words, it's of type (selected: GeoJSONItem[] | ((prevSelected: GeoJSONItem[]) => GeoJSONItem[])) => void. TypeScript understands this as long as you're using useState's return value directly, but when you then pass it to a context property that's explicitly typed as (selected: GeoJSONItem[]) => void, it requires only the array format.

    Changing the type of your producer function should fix this.

    See this answer.