cherniavskii.com

Using Leaflet in React apps

November 04, 2017|5 min read

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.

map

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: '&copy; <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.


Andrew Cherniavskii
Written by Andrew Cherniavskii, Frontend Engineer.