React-leaflet: Unable to use ref in `MapContainer`

Created on 8 Feb 2021  路  7Comments  路  Source: PaulLeCam/react-leaflet

Bug report

  • [x] All peer dependencies are installed: React, ReactDOM and Leaflet.
  • [x] Using a supported version of React and ReactDOM (v17.0.0 minimum).
  • [x] Using the supported version of Leaflet (v1.7.1 minimum) and its corresponding CSS file is loaded.
  • [x] Using the latest v3 version of React-Leaflet.
  • [x] The issue has not already been reported.
  • [x] Make sure you have followed the quick start guide for Leaflet.
  • [x] Make sure you have fully read the documentation and that you understand the limitations.

Expected behavior

I just updated my react-leaflet package from 2.7.0 to 3.1.0. Everything was fine with the older version. But when I upgraded the version to 3.1.0, I am unable to create the ref in <MapContainer />. My map container will appear in bootstrap modal, so it needed to be run invalidateSize function as below,

this.mapRef.current.leafletElement.invalidateSize(false)

But the mapRef always returning null. In previous version it was working fine.

<MapContainer
      ref={this.mapRef}
      center={[38.861, 71.2761]}
      zoom={5}
      zoomControl={false}
      style={{ width: "100%", height: "70vh" }}
    >
      <ZoomControl position="topright" />
      <TileLayer
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
      />
</MapContainer>

Actual behavior

The map ref value should not be none.

Steps to reproduce

My constructor is here for creating the ref

import React, { Component } from "react";
import {
  MapContainer,
  TileLayer,
  ZoomControl,
  WMSTileLayer,
} from "react-leaflet";

class EarList extends Component {
constructor(props) {
    super(props);
    this.mapRef = React.createRef();
  }

onViewClick = (id) => {
    this.setState({
      id: id,
      layer_name: ear.name,
    });
    setTimeout(
      () => this.mapRef.current.leafletElement.invalidateSize(false),
      1000
    );
  };

  render() {
      <button onClick={this.onViewClick.bind(this, id)} />
      {/* onViewClick bootstrap model */}
      <div className="modal fade map " tabIndex="-1" id="map">
          <div className="modal-dialog modal-lg">
            <div className="modal-content">
              <div className="modal-header">
                <h5 className="modal-title text-center"> layer</h5>
                <button
                  type="button"
                  className="close"
                  data-dismiss="modal"
                  aria-label="Close"
                >
                  <span aria-hidden="true">&times;</span>
                </button>
              </div>
              <MapContainer
                ref={this.mapRef}
                center={[38.861, 71.2761]}
                zoom={5}
                zoomControl={false}
                style={{ width: "100%", height: "70vh" }}
              >
                <ZoomControl position="topright" />

                <TileLayer
                  url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                  attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
                />
              </MapContainer>
            </div>
          </div>
        </div>
 };
};

Most helpful comment

Hi @Rennzie, Thank you very much for the solution. Now it is working fine without issue. In my case, I can access the set the invalidateSize to false as following,

//leafletElement no longer in use
setTimeout(
     () => this.mapRef.current.invalidateSize(false),
     1000
   );

All 7 comments

Hi @iamtekson,

Please see this issue for background https://github.com/PaulLeCam/react-leaflet/issues/806. Using a ref to access the map instance is no longer possible. My suggestion is to use the whenCreated prop to save a copy of the map instance to state and then use it as you normally would. Something like this should work in you case.

```javascript
import React, { Component } from "react";
import {
MapContainer,
TileLayer,
ZoomControl,
WMSTileLayer,
} from "react-leaflet";

class EarList extends Component {
constructor(props) {
super(props);
this.mapRef = React.createRef();
}

onViewClick = (id) => {
this.setState({
id: id,
layer_name: ear.name,
});

// first check mapRef.current is not not then proceed with the timeout
setTimeout(
() => this.mapRef.current.leafletElement.invalidateSize(false),
1000
);
};

render() {


// You could also set this to state
whenCreated={ mapInstance => { this.mapRef.current = mapInstance } }
center={[38.861, 71.2761]}
zoom={5}
zoomControl={false}
style={{ width: "100%", height: "70vh" }}
>

            <TileLayer
              url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
              attribution="&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
            />
          </MapContainer>
        </div>
      </div>
    </div>

};
};

```

Hi @Rennzie, Thank you very much for the solution. Now it is working fine without issue. In my case, I can access the set the invalidateSize to false as following,

//leafletElement no longer in use
setTimeout(
     () => this.mapRef.current.invalidateSize(false),
     1000
   );

In my case, I can access the set the invalidateSize to false as following...

Agree, no need for leafletElement in mapRef.current.leafletElement e.g. - it's implicated in mapInstance already.

Hello, I have a similar issue upgrading from v2 to v3. I already tried to following previous suggestion on this thread and also what is reported here, but without consistent result.

I am using tangramjs, gatsbyjs and leaflet and till react-leaflet v2 I did not experience issues, but after upgrading to v3 I am unable to get a ref to MapContainer component.

With v2 I did like that:

const MapGeoviz = () => {
    const mapRef = useRef();

    useEffect(() => {
        if (typeof window !== 'undefined'l) {
            const layer = Tangram.leafletLayer({
                scene,
            });

            layer.addTo(mapRef.current.leafletElement);
        }
    }, []);

    if (typeof window !== 'undefined') {
        return (
            <Map
                {...mapSettings}
                ref={mapRef}
            />

        );
    }

    return null;
};

Upgrading to v2 I try something like that following that:

const MapGeoviz = () => {
    const [map, setMap] = useState(null);

    useEffect(() => {
        if (typeof window !== 'undefined' && map !== null) {
            const layer = Tangram.leafletLayer({
                scene,
            });

            layer.addTo(map);
        }
    }, [map]);

    if (typeof window !== 'undefined') {
        return (
            <MapContainer
                {...mapSettings}
                whenCreated={setMap}
            />

        );
    }

    return null;
};

Doing like that I am experience no consistency, because sometime tangram layer has been rendered and others not completely.

Following this thread I also try like that:

const MapServices = () => {
const mapRef = useRef();

useEffect(() => {
    if (typeof window !== 'undefined') {
        const layer = Tangram.leafletLayer({
            scene,
        });

        layer.addTo(map.current);
    }
}, []);

if (typeof window !== 'undefined') {
    return (
        <MapContainer
            {...mapSettings}
            whenCreated={(mapInstance)=> { mapRef.current = mapInstance }}
        />
    );
}

return null;

};

With this solution I always get an undefined reference to map in layer.addTo(map.current);. If I also add map !== undefined to if statement inside useEffect, it does not rendering layer, so I think it does not get any ref.

I am doing something wrong, but can you point me how can I fix that?

Thanks!

Hi @lezan,

You'd be better of asking on Stack Overflow. The maintainer doesn't offer support in the issues. That said,

I'd roll your Tangram layer into a custom component and nest it within the map. Something like:

import {useMap} from 'react-leaflet';

const TangramLayer = () => {
   const map = useMap()

   useEffect(() => {
    if (typeof window !== 'undefined') {
        const layer = Tangram.leafletLayer({
            scene,
        });

        layer.addTo(map.current);
    }
   }, [map]);

  return null

}

const MapServices = () => {

    return (
        <MapContainer {...mapSetting} >
            <TangramLayer />
        </MapContainer>
    )
}

This blog of mine may give you some other ideas

Hello @Rennzie, I thought it might be related to what was also posted in this thread, sorry about that.

Meanwhile I found a similar solution using createTileLayerComponent and with a Tangram component nest it within the <MapContainer /> component. I followed this docs. I think it could be good as your solution, by the way I will give a try to your, because it is much simpler.

I really appreciate your answer, thanks! And sorry again for posting here.

No worries at all @lezan, only hoping you get an answer faster there.

I was also going to suggest trying a custom component, but glad you've got what you need.

Best of luck.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

How to use "unproject"?
acpower7 picture acpower7  路  4Comments

Marker With Label
Shadowman4205 picture Shadowman4205  路  4Comments

Flow 0.53.1 errors with 1.6.3 .flow files
diligiant picture diligiant  路  3Comments

Support for zoomend on Map component?
thenickcox picture thenickcox  路  4Comments

Markers temporarily drawn on wrong location, if marker is added during zoom animation
kojoa picture kojoa  路  3Comments