react-leaflet icon indicating copy to clipboard operation
react-leaflet copied to clipboard

"Map container is already initialized." error happens when component is in Suspense

Open jisuo opened this issue 3 years ago • 3 comments

Bug report in v4

Before opening an issue, make sure to read the contributing guide and understand this is a bug tracker, not a support platform.

Please make sure to check the following boxes before submitting an issue.
Issues opened without using this template will be closed unless they have a good reason not to follow this template.

  • [x] All peer dependencies are installed: React, ReactDOM and Leaflet.
  • [x] Using the latest version of React and ReactDOM v18.
  • [x] Using the supported version of Leaflet (v1.8.0 minimum) and its corresponding CSS file is loaded.
  • [x] Using the latest v4 version of React-Leaflet.
  • [x] The issue has not already been reported.
  • [x] Make sure you have followed the quick start guide for Leaflet.
  • [x] Make sure you have fully read the documentation and that you understand the limitations.

Expected behavior

The error "Map container is already initialized." should not happen when Suspense happens.

Actual behavior

The error "Map container is already initialized." happens when Suspense happens.

Steps to reproduce

Code example to reproduce using react-query to trigger Suspense state. Click the Button to trigger fake api fetch to trigger Suspense state.

https://codesandbox.io/s/modest-noyce-qy21ix?file=/src/Map.tsx](https://codesandbox.io/s/modest-noyce-qy21ix?file=/src/Map.tsx:1017-1126)

jisuo avatar Mar 21 '23 09:03 jisuo

As a workaround I added a key prop to <MapContainer key={data.imageUrl}> so whenever the data.imageUrl is updated and a Suspense is triggered the key prop will destroy the instance and recreate it. Maybe this workaround will help others until there are additional ref-checks added to the MapContainer to prevent the error.

This useCallback in MapContainer is run again on Suspense and since context is null it causes it to re-create the map in the same div:

const mapRef = useCallback((node)=>{
        if (node !== null && context === null) {
            const map = new LeafletMap(node, options);
            if (center != null && zoom != null) {
                map.setView(center, zoom);
            } else if (bounds != null) {
                map.fitBounds(bounds, boundsOptions);
            }
            if (whenReady != null) {
                map.whenReady(whenReady);
            }
            setContext(createLeafletContext(map));
        }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);
    ```

jisuo avatar Mar 27 '23 09:03 jisuo

Another (similar) workaround found in #936 is using key={new Date().getTime()} in MapContainer.

Edit: Here's another workaround. The one above was causing my map to reload and reset the zoom level every time I interacted with a Marker.

const time = useMemo(() => new Date().getTime(), [])

return (
    <MapContainer key={time} />
)

xharris avatar Apr 17 '23 14:04 xharris

Hi, I encountered this issue and fixed it by adding a Suspense boundary in the MapContainer itself because the loading components were part of the MapContainer children.

<MapContainer>
  <Suspense>
    ...children
  </Suspense>
</MapContainer>

vincent-raman avatar Feb 19 '24 18:02 vincent-raman