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
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: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.
you can use the
useEffect
to watch for changes in the prop and trigger the update of the map layers. You can also use theuseMemo
hook to memoize thegeoJsons
array, for performance.this should now update immediately when the
displayUploadedFiles
prop changes, without the need to pan the map.