I am using react-leaflet version 4.x.
I'm trying to update the zoom level of the map on click of button, just as a simple use case. I realize that you could create a child component to <MapContainer>
and use useMap()
but I want to be able to alter the map with controls located outside of the <MapContainer>
component. As such, I am using react-redux to try and manage the state.
I have created a slice with the zoom level as part of the initial state:
import { createSlice } from "@reduxjs/toolkit";
export const mapViewSlice = createSlice({
name: "mapView",
initialState: {
zoom: 5,
},
reducers: {
changeZoom: (state, action) => {
state.zoom = action.payload;
},
},
});
export const { changeZoom } = mapViewSlice.actions;
export default mapViewSlice.reducer;
I then created the Map
component where I dispatch the changeZoom
action on click of a button:
import React, { useState, useEffect, useRef } from "react";
import { useSelector, useDispatch } from "react-redux";
import { MapContainer, TileLayer } from "react-leaflet";
import { changeZoom } from "./mapViewSlice";
import "./Map.css";
export const Map = () => {
const mapRef = useRef(null);
const zoomLevel = useSelector((state) => state.mapView.zoom);
const dispatch = useDispatch();
return (
<div id="map-container" className="flex-container-row">
<MapContainer center={[37.0902, -95.7129]} zoom={zoomLevel} ref={mapRef}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
<button onClick={() => dispatch(changeZoom(2))}>Change Zoom</button>
</div>
);
};
If I click the button and console.log(zoomLevel)
I see that it returns the value 2
as expected. But I need to update the map reference. I have tried putting in a useEffect
to update the map, but I get an error that suggests the mapRef
is not working right:
useEffect(() => {
mapRef.current.setZoom(zoomLevel);
}, [zoomLevel]);
Cannot read properties of null (reading 'setZoom') TypeError: Cannot read properties of null (reading 'setZoom')
I have also tried changing the ref to whenReady
and using useState
instead to save the map reference:
const [map, setMap] = useState(null)
//...
useEffect(() => {
if (map) {
console.log(map);
map.setZoom(zoomLevel);
}
}, [zoomLevel]);
//...
<MapContainer center={[37.0902, -95.7129]} zoom={zoomLevel} whenReady={setMap}>
//...
</MapContainer>
That gives me a error like this:
map.setZoom is not a function
How do I do this?
Edit: I have to be missing an import from leaflet to get access to the setZoom
function, right? How do I get those methods?
Turns out all I needed to do was set the ref
in <MapContainer>
and use it with useState
and not useRef
. I found in the react-leaflet docs example of controlling the map with an external control.
My final Map
component code:
import React, { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { MapContainer, TileLayer } from "react-leaflet";
import { changeZoom } from "./mapViewSlice";
import "./Map.css";
export const Map = () => {
const [map, setMap] = useState(null);
const zoomLevel = useSelector((state) => state.mapView.zoom);
const dispatch = useDispatch();
useEffect(() => {
if (map) {
map.setZoom(zoomLevel);
}
}, [zoomLevel, map]);
return (
<div id="map-container" className="flex-container-row">
<MapContainer center={[37.0902, -95.7129]} zoom={zoomLevel} ref={setMap}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
<button onClick={() => dispatch(changeZoom(2))}>Change Zoom</button>
</div>
);
};