0

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='&copy; <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?

1 Answer 1

0

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='&copy; <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>
  );
};

Not the answer you're looking for? Browse other questions tagged or ask your own question.