Show All Cluster Markers in react-leaflet-markerclusterer using React Typescript

My React Typescript app is displaying a Leaflet map with marker clusters using react-leaflet & react-leaflet-markerclusterer.

However, I am unable to get the map to show all cluster markers inside the map view. I am attempting to convert this JS solution to Typescript.

One of the errors shown in the. JS console is

React Hook "useMap" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function

In VSCode, there is also a Typescript error

Object is possibly ‘undefined’.ts(2532)

on the line

const groupBounds = group.getBounds();

Why is this happening, and what is the proper way to write this Typescript code?

import React, { useEffect, useRef } from 'react';
import { MapContainer, TileLayer, Marker, useMap } from 'react-leaflet';
import MarkerClusterGroup from 'react-leaflet-markercluster';

interface IProps {
    markers: {
        lat: number;
        lon: number;
    }[];
    
    mapCenter: {
        lat: number;
        lon: number;
    };
}

export function MapView({markers, mapCenter}: IProps): JSX.Element {

    const groupRef = useRef();
    
    useEffect(() => {
        if (groupRef !== undefined) {
            const group = groupRef.current;
            const map = useMap();                   // ERROR: React Hook "useMap" cannot be called inside a callback.
            const groupBounds = group.getBounds();  // ERROR: Object is possibly 'undefined'.ts(2532)
            map.fitBounds(groupBounds);
        }
    }, []);

    return (
        <MapContainer
            center={[mapCenter.lat, mapCenter.lon]}
            zoom={10}
            style={{ width:'100%', height:'100vh' }}
            className='markercluster-map'
        >
            <TileLayer 
                url={`https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=${access_token}`}
            />
            <MarkerClusterGroup ref={groupRef}>
                {
                    markers.map((marker, index) => (
                        <Marker 
                            position={[marker.lat, marker.lon]} 
                            key={index}
                        />
                    ))
                }
            </MarkerClusterGroup>
        </MapContainer>
    )
}

47 thoughts on “Show All Cluster Markers in react-leaflet-markerclusterer using React Typescript”

  1. This is not a typescript issue, this is a react issue. Hooks need to be called either within the top level of the function body, or as part of another custom hook. The same applies to react-leaflet’s useMap. You can move it outside the useEffect. However a useMap hook call must be part of a component that is a child of the MapContainer so that it has access to the leaflet context object. You can abstract this logic into its own component, like in this example, but I think you’re better of using a whenCreated with a state variable to grab an instance of the L.Map and use it in the effect:

    export function MapView({markers, mapCenter}: IProps): JSX.Element {
    
        const groupRef = useRef();
        const [map, setMap] = useState();
    
        useEffect(() => {
            if (groupRef && map) {
                const group = groupRef.current;
                const groupBounds = group?.getBounds();
                map.fitBounds(groupBounds);
            }
        }, [map, groupRef]);
    
        return (
            <MapContainer
              whenCreated={map => setMap(map)}
              moreProps={moreProps}
            >
               <Stuff />
            </MapContainer>
        )
    
    }
    

    If TS is still complaining that group or map is undefined, you can use the optional property operator ?. on them:

    if (groupRef && map) {
      const group = groupRef?.current;
      const groupBounds = group?.getBounds();
      map?.fitBounds(groupBounds);
    }
    

    Typescript should be smart enough to know that groupRef and map are defined, as they’re in the if statement, but it may still complain about group being defined. You could also do if (groupRef && groupRef.current && map) to remove all doubt.

    Reply

Leave a Comment