skip to Main Content

I am using the react-map-gl Map component in my project. I pass in via props props.displayUploadedFiles, an array of GeoJSON objects. I want to display these GeoJSON objects on my Map using react-map-gl’s Source and Layer components. However, when I upload files, the only way to see the data from the files on the map is if I move/pan the map in order to force a re-render.

I would expect that I could use a hook to say "when this prop changes, change the Sources and Layers within my Map and display them." However, instead of displaying immediately, the hook does not seem to trigger until I pan the map.

I can confirm that the files are uploaded and in proper GeoJSON format, and that the props passed into the Map component are updating as the props update.

Here is an example of how the props are passed through:

// App.tsx
export default function App(): JSX.Element {
  const [files, setFiles] = useState<any[]>([]);
  const setFilesState = (filesUploaded: File[]) => {
    const jsons = kmlToGeoJson(filesUploaded);
    setFiles(jsons);
  };

  return (
    <>
      <MyMap displayUploadedFiles={files} ... />
      <MyFileUploader fileStateUpdater={setFilesState} />
    </>
  );
}

//MyMap.tsx
function MapboxMap(props: MapboxMapProps): JSX.Element {
  const geoJsons = useMemo(() => (props.displayUploadedFiles.map((file) => {
    const id = JSON.stringify(file.features);
    return (
      <Source key={id} type="geojson" data={file}>
        <Layer key={id} {...pointLayer} />
      </Source>
    );
  })), [JSON.stringify(props.displayUploadedFiles)]); // runs on upload once I move the map

  return (
    <Map mapboxAccessToken={mapboxToken} ...>
      {geoJsons}
      ...
    </Map>
  );
}

function MyMap(props: StyledMapProps): JSX.Element {
  return (
    <div id={sx.styledMap}>
      <MapboxMap ... displayUploadedFiles={props.displayUploadedFiles} />
      ...
  );
}

export default MyMap;


This post seems very similar to my problem, but for class-based instead of functional design. It indicates that I should use useEffect(). However, I could not get that to work–it had the same behavior I described above. The minimal example has useMemo() as I tried that as well.

The minimal example also shows the useMemo() dependency as [JSON.stringify(props.displayUploadedFiles)], because I thought that just passing in the array might have been the problem, as the reference might not change even if the contents did, meaning it wouldn’t re-render. However, this did not change the behavior.

I have also tried changing where MyFileUploader is located, such that the props are not passed from App.tsx, but rather the logic is contained within either MyMap or MapboxMap, but they don’t change the behavior. In any situation, the props always update properly, but the hook that depends on it does not get called until I pan the map.

2

Answers


  1. Chosen as BEST ANSWER

    I never did figure out why the re-render wasn't being triggered properly by the useEffect() call as we would expect it to. However, I figured out a workaround for those interested.

    in the MapboxMap component, I added this:

      const mapRef = useRef(null);
      useEffect(() => {
        if (mapRef?.current) {
          mapRef.current.zoomTo(mapRef.current.getZoom());
        }
      }, [props.displayUploadedFiles]);
    

    It goes into the underlying mapbox-gl map object and, whenever more files are updated, it "zooms in" to the current zoom level. This has the effect of if I were to pan the map manually, but automates it. It forces the map to re-render and makes it so the user can't perceive the viewport change.


  2. you can use the useEffect to watch for changes in the prop and trigger the update of the map layers. You can also use the useMemo hook to memoize the geoJsons array, for performance.

    import React, { useState, useEffect, useMemo } from 'react';
    import { Map, Source, Layer } from 'react-map-gl';
    
    function MapboxMap(props) {
      const geoJsons = useMemo(() => (
        props.displayUploadedFiles.map((file) => {
          const id = JSON.stringify(file.features);
          return (
            <Source key={id} type="geojson" data={file}>
              <Layer key={id} {...pointLayer} />
            </Source>
          );
        })
      ), [props.displayUploadedFiles]);
    
      // Use useEffect to trigger the map update when props change
      useEffect(() => {
        // Force a map update when the displayUploadedFiles prop changes
        // This will re-render the map layers immediately
        // You may also need to handle map updates here if required
      }, [props.displayUploadedFiles]);
    
      return (
        <Map mapboxAccessToken={mapboxToken} ...>
          {geoJsons}
          {/* ... */}
        </Map>
      );
    }
    
    export default MyMap;
    

    this should now update immediately when the displayUploadedFiles prop changes, without the need to pan the map.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search