Search code examples
reactjstypescriptleafletreact-leaflet

How to set the reference to the 'leafletElement' using Typescript inside react-leaflet?


The problem is that while using TS in my React project I need to set the type of the useRef() function, but I don't know what it is. So as in the code below I'm setting it to any and calling the leafletElement afterwards. The problem that occurs is that the element is always undefined even after the map is referenced through the whenCreated function isnide MapContainer.

I am trying to set the MapContainer in a way that it shows all the Marker making it dynamically resizable so when the location is changed it updates it and adjusts the MapContainer because it does not have a ref property.

Example I'm trying to reproduce

const MapTry = () => {
const [nodes, setNodes] = useState<INode[]>([]);
const [devices, setDevices] = useState<IDevice[]>([]);

const [nodeDeviceTest, deviceNodeConnection] = useState([]);
var arrObj: any = [];
var arrOfBounds: any = [];
var mapRef = useRef<any>();
var groupRef = useRef<any>();


var device = new L.Icon({
    iconUrl: deviceCar,
    iconSize: [30, 30],
    iconAnchor: [16, 3],
    popupAnchor: [1, -1],
    shadowSize: [41, 41]
});

var deviceNode = new L.Icon({
    iconUrl: nodeDevice,
    iconSize: [20, 30],
    iconAnchor: [10, 0],
    popupAnchor: [1, -34],
    shadowSize: [41, 41],

})

useEffect(() => {
    async function fetchData() {
        const user = checkUserData();
        const project = checkProjectData();
        debugger;
        if (mapRef.current && mapRef) {
            let map = mapRef.current.leafletElement;
            let group = groupRef.current.leafletElement;
            map.fitBounds(group.getBounds());
        }



        const result = await getProjectNodeDeviceMap(project, user);
        console.log(result);
        if (result) {
            if (result.devicesToNodes) {
                for (var i = 0; i < result.devicesToNodes.length; i++) {
                    var objec = {
                        nodeId: "",
                        devId: "",
                        deviceLat: 0,
                        deviceLong: 0,
                        nodeLat: 0,
                        nodeLong: 0
                    }
                    for (var j = 0; j < result.devices.length; j++) {
                        if (result.devicesToNodes[i].deviceId == result.devices[j].deviceId) {
                            objec.deviceLat = result.devices[j].latitude
                            objec.deviceLong = result.devices[j].longitude
                            objec.devId = result.devices[j].deviceId
                            arrOfBounds.push([result.devices[j].latitude, result.devices[j].longitude])

                            for (var k = 0; k < result.nodes.length; k++) {
                                if (result.devicesToNodes[i].nodeId == result.nodes[k].id) {
                                    objec.nodeLat = result.nodes[k].latitude
                                    objec.nodeLong = result.nodes[k].longitude
                                    objec.nodeId = result.nodes[k].id
                                    arrOfBounds.push([result.nodes[k].latitude, result.nodes[k].longitude])


                                    arrObj.push(objec);
                                    break;
                                }
                            }
                        }
                    }
                    deviceNodeConnection(arrObj);
                }
            }

            if (result.nodes) {
                result.nodes.map((node: INode) => {
                    if (node.state == "running") {
                        node.color = "success";
                    }

                    else if (node.state == "error") {
                        node.color = "danger";
                    }

                    else if (node.state == "stopped") {
                        node.color = "warning";
                    }

                    else {
                        node.color = "info";
                    }
                })
            }

            setNodes(result.nodes)
            setDevices(result.devices)
        }
    }

    const timer = setInterval(() => {
        fetchData();
    }, 3000)

    return () => clearTimeout(timer);
}, [arrObj])

return (
    <div className="col-md-9">
        <div className="card shadow p-3 mb-5 bg-white rounded">
            <div className="text-center">
                <h3>Showcase</h3>
            </div>
            {devices && devices.length > 0 && <MapContainer
                whenCreated={mapInstance => { mapRef.current = mapInstance }}
                center={[devices[0].latitude, devices[0].longitude]}
                zoom={6}
                scrollWheelZoom={true}
                minZoom={2}
                maxBoundsViscosity={1.0}
                zoomControl={false}
                style={{ backgroundColor: "inherit", width: "100%", height: "60vh" }}
            >
                <TileLayer
                    attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
                    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                />
                {nodeDeviceTest.length > 0 && nodeDeviceTest?.map((point: any) => {
                    return <Polyline key={point.deviceId} positions={[
                        [point.nodeLat, point.nodeLong], [point.deviceLat, point.deviceLong]
                    ]} color={'blue'} dashArray="5,10" />
                })}
                <FeatureGroup ref={groupRef}>
                    {nodes?.map((point: any) => (
                        <Marker
                            zIndexOffset={100}
                            icon={deviceNode}
                            key={point.Id}
                            position={[
                                point.latitude,
                                point.longitude
                            ]}
                        >
                            <Popup>
                                <br />
                                Node: {point.name}
                                <br />
                                State:
                                <span className={`text-${point.color}`}>
                                    <b>{point.state}</b>
                                </span>
                            </Popup>
                        </Marker>
                    ))}
                    {devices?.map((dev: any) => {
                        return <Marker
                            key={dev.deviceId}
                            position={[
                                dev.latitude,
                                dev.longitude
                            ]}
                            icon={device}
                        >
                            <Popup>
                                Device id: {dev.deviceId}
                            </Popup>
                        </Marker>
                    })}
                </FeatureGroup>
            </MapContainer>}
        </div>
    </div>

)}

Also i hope the FeatureGroup is properly referenced.Sorry if I missed something in my explanation. I am fairly new to React/TS.

EDIT: This is the example code I created

const Mapp = () => {
const devices = [{
    deviceId: "1-528a-4f80-9c7a-124108f86895",
    latitude: 45.5,
    longitude: 18.7
},
{
    deviceId: "2-528a-4f80-9c7a-124108f86895",
    latitude: 60.5,
    longitude: 18.7
}]

const nodes = [{
    id: "test-node-1",
    latitude: 45,
    longitude: 15.4566,
    name: "TestNode1",
    state: "running",
},
{
    id: "test-node-2",
    latitude: 47,
    longitude: 15.4566,
    name: "TestNode2",
    state: "running",
}]

const devicesToNodes = [{
    deviceId: "1-528a-4f80-9c7a-124108f86895",
    nodeId: "test-node-1"
},
{
    deviceId: "2-528a-4f80-9c7a-124108f86895",
    nodeId: "test-node-1"
}]

const arrOfLatLang = [{
    deviceLat: 45.5,
    deviceLong: 18.7,
    nodeLat: 47,
    nodeLong: 15.4566
},
{
    deviceLat: 60.5,
    deviceLong: 18.7,
    nodeLat: 47,
    nodeLong: 15.4566
}]


return (
    <MapContainer
        style={{ marginLeft: "200px" }}
        center={[devices[0].latitude, devices[0].longitude]}
        zoom={6}
        scrollWheelZoom={true}
        bounds={[[90, 180], [-90, 180]]}
        minZoom={2}
        maxBoundsViscosity={1.0}
    >
        <TileLayer
            attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        {arrOfLatLang.length > 0 && arrOfLatLang?.map((point: any) => {
            return <Polyline key={point.deviceId} positions={[
                [point.nodeLat, point.nodeLong], [point.deviceLat, point.deviceLong]
            ]} color={'blue'} dashArray="5,10" />
        })}

        {nodes?.map((point: any) => (
            <Marker
                zIndexOffset={100}
                key={point.Id}
                position={[
                    point.latitude,
                    point.longitude
                ]}
            >
                <Popup>
                    <br />
                    Node: {point.name}
                    <br />
                    State:
                    <span className={`text-${point.color}`}>
                        <b>{point.state}</b>
                    </span>
                </Popup>
            </Marker>
        ))}
        {devices?.map((dev: any) => {
            return <Marker
                key={dev.deviceId}
                position={[
                    dev.latitude,
                    dev.longitude
                ]}
            >
                <Popup>
                    Device id: {dev.deviceId}
                </Popup>
            </Marker>
        })}
    </MapContainer>
)}

Solution

  • You use the underlying leaflet instance type. For the map, its L.Map, for a featuregroup, its L.FeatureGroup:

    var mapRef = useRef<L.Map>();
    var groupRef = useRef<L.FeatureGroup>();
    

    Note that setting the map ref in whenCreated, like you're doing with whenCreated={mapInstance => { mapRef.current = mapInstance }}, is not a great idea, and may cause problems with the way you're trying to type the refs. I recommend using a proper ref prop on the MapContainer (ref={mapRef}), or setting the mapRef as a state variable instead, as described in the example in the react-leaflet docs.