Using Leaflet in React apps
This article uses class components and lifecycle hooks to integrate React and Leaflet. If you’re curious how to do that using React Hooks — see Using Leaflet in React apps: React Hooks
Leaflet is a JavaScript library for creating maps. In this article you will learn how to use Leaflet in React applications.
React uses Virtual DOM, which creates diffs of previous and actual DOM structure and updates DOM when it is necessary. It means, that React is responsible for DOM updates.
On the contrary, Leaflet has imperative API and manipulates DOM directly. Because of this difference React and Leaflet integration may not be intuitive at first.
There already is awesome react-leaflet library, which provides React components for Leaflet. Unfortunately, it does not suite my needs - I have a map module which extends Leaflet, uses Leaflet plugins and adds a lot of custom functionality (like custom controls, built-in layers and other stuff) to map.
In order to use my map module with react-leaflet
, I should have been write
React components for all custom functionality and plugins by extending
react-leaflet
components.
Also, when it comes to maps, I rather prefer imperative JavaScript API. Not to mention, that adding more dependencies to my project is not cool — mostly because of increased bundle size.
So I decided to use my map module as is, and integrate it with React by myself.
Creating simple map
Let’s say, I want to create a Map
component, which renders a map. It will look
like this:
import React from "react";
import L from "leaflet";
class Map extends React.Component {
componentDidMount() {
// create map
this.map = L.map("map", {
center: [49.8419, 24.0315],
zoom: 16,
layers: [
L.tileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
attribution:
'© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
}),
],
});
}
render() {
return <div id="map"></div>;
}
}
export default Map;
Adding marker to map
Now I’ll modify componentDidMount
method by adding code that creates a marker
and adds it to map:
componentDidMount() {
...
// add marker
this.marker = L.marker(this.props.markerPosition).addTo(this.map);
}
Marker’s position is passed through markerPosition
prop to Map
component.
Reference to newly created marker instance is stored in this.marker
.
But what happens when markerPosition
prop changed? Will Map
handle this and
update marker’s position? The answer is no.
componentDidMount
method is only called once — when component is mounted to
the DOM. We should handle props changes separately by using other React
lifecycle method — componentDidUpdate
. It is called every time component
updates - after props or state changes.
Updating marker position
Let’s update marker’s position every time markerPosition
prop is changed:
componentDidUpdate({ markerPosition }) {
// check if position has changed
if (this.props.markerPosition !== markerPosition) {
this.marker.setLatLng(this.props.markerPosition);
}
}
While this article doesn’t aim to cover Leaflet setup, you need to include Leaflet CSS file in your project to make map display properly. One of possible ways to do that is to include CSS style sheet from CDN — see Leaflet Download page for example.
Adding markers layer to map
Another useful use case for Map
component may be a layer, which contains
markers. I want to pass array of markers data to Map
and expect Map
to
update markers every time data is changing.
Implementation will be very similar. I’ll create a LayerGroup
, which will
contain all markers, and store it’s reference in this.layer
:
componentDidMount() {
...
// add layer
this.layer = L.layerGroup().addTo(this.map);
}
Then I create updateMarkers
method, which will receive markersData
as an
argument. It will clear all markers and add new markers from markersData
:
updateMarkers(markersData) {
this.layer.clearLayers();
markersData.forEach(marker => {
L.marker(
marker.latLng,
{ title: marker.title }
).addTo(this.layer);
});
}
Note, that
updateMarkers
method implementation is up to you — you can optimize it to not remove markers which are still present in new data, or update existing markers instead of creating new ones.
Then I have to call updateMarkers
in two places . First one
iscomponentDidMount
method:
componentDidMount() {
...
// add layer
this.layer = L.layerGroup().addTo(this.map);
this.updateMarkers(this.props.markersData);
}
Second one is componentDidUpdate
method:
componentDidUpdate({ markersData }) {
// check if data has changed
if (this.props.markersData !== markersData) {
this.updateMarkers(this.props.markersData);
}
}
Note, that I only compare references with strict equality operator to know if data has changed. I assume that my data is immutable and I newer mutate it. This is the proper way to update state in React.
Now Map
component will react on data change and update markers.
Summary
In this article I wanted to show, that it’s possible to use Leaflet in React apps and it’s not that hard. Also, it may be a good example of how to use imperative libraries in declarative React.
Thanks for reading.
EDIT: I’ve updated code examples to use componentDidUpdate
over
componentWillReceiveProps
for performing side-effects, since the latter would
be deprecated in the future versions of React. Learn more about lifecycle
changes in React
docs.
Written by Andrew Cherniavskii, Software Engineer.