PaulLeCam / react-leaflet

React components for Leaflet maps
https://react-leaflet.js.org
Other
5.16k stars 886 forks source link

Nextjs13 and react leaflet: Dynamic loaded Marker in FeatureGroup unable to call getBounds #1090

Closed a-marcel closed 1 month ago

a-marcel commented 1 year ago

Bug report in v4

I currently play around with the new nextjs13 and the app directory as well as server side only code. In my project i use React leaflet and this cannot be directly used anymore in Nextjs13 (with the app directory). It is required to load the client components differently or via dynamic. In my project i use the 2nd option via dynamic and created a File for the components that can be lazy loaded:

  const ClientSideMap = dynamic(() => import('@components/Map'), { ssr: false })

    type Props = {
        center?: number[];
        locationValue?: string;
        children?: ReactNode;
    };

    const ClientMap = forwardRef((props : Props, mapRef) => (
        <ClientSideMap {...props} forwardedRef={mapRef} />
    ))
    ClientMap.displayName = 'ClientMap';

    export default ClientMap;

    const ClientSideMarker = dynamic(() => import('@components/Marker'), { ssr: false })

    type MarkerProps = {
        center?: number[];
        locationValue: L.LatLngExpression;
        children?: ReactNode;
        id: string | number;
    };

    export const Marker = forwardRef(({ center, locationValue, children, id }: MarkerProps, markerRef) => {
        return (
            <ClientSideMarker locationValue={locationValue} id={id} ref={markerRef}>
                <Popup>
                    {children}
                </Popup>
            </ClientSideMarker>
        )
    })
    Marker.displayName = 'Marker'

These dynamic components are loaded in the app/page.tsxfile

import Map, { Marker } from '@components/MapLazyComponents'

export const dynamic = "force-dynamic";

export default async function Home() {
    const result = await getData() || {};

    // console.log("Data", result)

    return (
        <main>
            <div style={{
                width: '400px',
                height: '400px'
            }}>
                <Map>
                    {result.map(m => (
                        <Marker key={m.id} locationValue={[m.lat as number, m.lng as number]} id={m.id}>{m.id} - {m.name}</Marker>
                    ))}
                </Map>
            </div>
        </main>
    );
}

When I run this project, it works like expected and i can see a map with the configured markers.

Actual behavior

I would like to set the Zoom the map in that way, that all Markers are visible. Leaflet offer a possibility for that use-case by using getBounds.

useEffect(() => {
    console.log("Map test", featureGroupRef.current, featureGroupRef.current?.getLayers())
    if (map && featureGroupRef.current) {
      console.log("Bounds", featureGroupRef.current)
      // map.fitBounds(featureGroupRef.current.getBounds());
    }
  }, [map, featureGroupRef]);

Unfortunately, this is not working (Error: Error: Bounds are not valid. - Thrown in that Line). After some debugging i assume that this is reladed to the dynamic loading of Markers. In the code lines above, you can see that i log some lines after the Map is loaded. The Layers the Map is empty but in the console.log it's possible to see that there is an array with layers that contains the Marker.

Thanks a lot

P.s.: I know that I can change the way and pass the marker data via Json down to the MapComponent without a use of dynamic. But I really would like to use it in the way where everything is prepared in the page.tsxfile - so on the server side.

Expected behavior

The map should set the boundaries where all marker fit.

Steps to reproduce

Since this is a more complex problem, I created a github repo where the issue is visible. https://github.com/a-marcel/nextjs13-app-dynamic-loaded-marker/tree/main

PaulLeCam commented 1 month ago

This is a client-only library, whatever is needed for server-side rendering is out of scope for the library and up to your app logic.