Leigh Halliday Leigh Halliday is a developer based out of Canada who works at FlipGive. He writes about React and Ruby on his blog and publishes React tutorials on YouTube.

React Leaflet tutorial

4 min read 1381

React Leaflet Tutorial

Editor’s note: This post was last updated on 22 October 2021 to improve code and update any outdated information.

Most developers are familiar with Google Maps and MapBox, but both require accounts to use them and can require a paid subscription for certain features. What if you wanted an open source and free alternative? Here’s where Leaflet steps up to the plate!

Leaflet is a lightweight, open source mapping library that utilizes OpenStreetMap, a free editable geographic database.

In this article, we’ll see how to use React Leaflet to render Leaflet maps inside a React app. We’ll show markers with custom icons and display a popup on the map when clicked. Later, we will see what needs to change to load remote vs. local data using SWR.

Getting started

Let’s begin by creating a React app, then move into its directory. After that install stable versions of react-leaflet and leaflet with the following commands:

npx create-react-app react-leaflet-demo
cd react-leaflet-demo
# install react-leaflet and leaflet
npm install [email protected] [email protected]

Before rendering the app, replace the "browserslist" values in the package.json file with:

"browserslist": [
  ">0.2%",
      "not dead",
      "not op_mini all"
    ]

Doing so will prevent this error below from popping up:

Module parse failed: Unexpected token (10:41)
File was processed with these loaders:
    ./node_modules/babel-loader/lib/index.js
    You may need an additional loader to handle the result of these loaders.
> | useEffect(function updatePathOptions() {
> | if (props.pathOptions !== optionsRef.current) {
>  const options = props.pathOptions ?? {};
> | element.instance.setStyle(options);
> | optionsRef.current = options;

Running npm start should render the app with an affirmative message on your browser.

How to install React Leaflet

After adding react-leaflet to our package.json file, we must display our map correctly. React Leaflet requires some CSS to render, and we can either include the CSS link tag in head or we can copy and paste the CSS from the file below directly into the project:

<link
  rel="stylesheet"
  href="https://unpkg.com/[email protected]/dist/leaflet.css"
  integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
  crossorigin=""
/>

We need to also to set the width and height of .leaflet-container that the map renders itself into, otherwise it won’t be visible because the div will have a height of 0px:

.leaflet-container {
  width: 100%;
  height: 100vh;
}

Once this is done, we’re ready to get started! The code below shows the minimal amount required to get a Leaflet map rendering in our React app.

We must import Map from react-leaflet (along with some other packages that we’ll utilize later), and we’ll return it from our App component:

import React, {useState} from "react";
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import './App.css';
import { Icon } from "leaflet";
import * as parkData from "./data/skateboard-parks.json";

function App() {
  return (
    <MapContainer center={[45.4, -75.7]} zoom={12}scrollWheelZoom={false}>
      <TileLayer
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      />
    </MapContainer>
  );
}

export default App;

The MapContainer component requires that we set a center position, which is an array containing latitude and longitude, along with the default zoom level of the map.

You’ll also notice the TileLayer component nested inside MapContainer. We are required to give attribution to OpenStreetMap, otherwise, all you’ll see is a grey square on the screen:

How do you add a marker in Leaflet?

To display markers on the map we need some data. Our data comes from the city of Ottawa, containing the location of skateboard parks in the area. Let’s load this data locally from a JSON file, but, to get an idea of what it looks like, here’s an example of two skateparks below:

{
  "features": [
    {
      "type": "Feature",
      "properties": {
        "PARK_ID": 960,
        "NAME": "Bearbrook Skateboard Park",
        "DESCRIPTIO": "Flat asphalt surface, 5 components"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [-75.3372987731628, 45.383321536272049]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "PARK_ID": 1219,
        "NAME": "Bob MacQuarrie Skateboard Park (SK8 Extreme Park)",
        "DESCRIPTIO": "Flat asphalt surface, 10 components, City run learn to skateboard programs, City run skateboard camps in summer"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [-75.546518086577947, 45.467134581917357]
      }
    }
  ]
}

With our data in place, we can map through it inside of the MapContainer component, returning a Marker component for each of the park locations. A Marker requires a position prop, telling it where to render on the map.

This is an array of [latitude, longitude], much like the center prop of MapContainer.

In addition to this, we must set up some state. Inside the onClick prop, we let’s set the activePark when a user clicks on the marker. We’ll use this later to show some information to the user about a specific skatepark in a map popup:

 //App.js
export default function App() {
  const [activePark, setActivePark] = useState(null);

  return (
    <MapContainer center={[45.4, -75.7]} zoom={12} scrollWheelZoom={false}>
  {parkData.features.map(park => (
        <Marker
          key={park.properties.PARK_ID}
          position={[
            park.geometry.coordinates[1],
            park.geometry.coordinates[0]
          ]}
          onClick={() => {
            setActivePark(park);
          }}
          icon={icon}
        />
      ))}
    </MapContainer>
  );
}

Displaying map popups with React Leaflet

Because we are tracking which skatepark the user clicks on, if there is an activePark in our state, we can show a popup.

The Popup component shows a little white bubble that can close, and much like a Marker, we’re required to give it a position so it knows where to render on the map. Inside of the Popup we’re able to pass HTML.



This can also be styled using CSS, so feel free to change the look and feel to get it looking exactly like you want.

There is an onClose prop event on the Popup, allowing us to track when the user closes the popup bubble so we can update the state accordingly:

//App.js
<MapContainer center={[45.4, -75.7]} zoom={12} scrollWheelZoom={false}>
  {activePark && (
    <Popup
      position={[
        activePark.geometry.coordinates[1],
        activePark.geometry.coordinates[0]
      ]}
      onClose={() => {
        setActivePark(null);
      }}
    >
      <div>
        <h2>{activePark.properties.NAME}</h2>
        <p>{activePark.properties.DESCRIPTIO}</p>
      </div>
    </Popup>
  )}
</MapContainer>

Adding React Leaflet custom marker icons

It is easy to customize marker icons in Leaflet by using Icon, imported from leaflet itself. With that, we can create a new Icon instance, setting the URL location of the image along with its size:

import { Icon } from "leaflet";

const skater = new Icon({
  iconUrl: "/skateboarding.svg",
  iconSize: [25, 25]
});

The Marker component has an icon prop that can be set to the skater variable we created.

Displaying remote data with React Leaflet

Using SWR for remote data fetching, we can load our data remotely from an API endpoint. Once we have the data, how we display it on the map is no different from displaying local data. To add this concept, we will display some crime data provided by the UK police.

I have sliced the data to only render the first 100 crimes in the array because rendering more than 1000 markers slows the map:

// existing imports + new import for SWR
import useSwr from "swr";

const fetcher = (...args) => fetch(...args).then(response => response.json());

function App() {
  const url =
    "https://data.police.uk/api/crimes-street/all-crime?lat=52.629729&lng=-1.131592&date=2019-10";
  const { data, error } = useSwr(url, { fetcher });
  const crimes = data && !error ? data.slice(0, 100) : [];

  return (
    <MapContainer center={[52.6376, -1.135171]} zoom={12} scrollWheelZoom={false}>
      <TileLayer
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      />

      {crimes.map(crime => (
        <Marker
          key={crime.id}
          position={[crime.location.latitude, crime.location.longitude]}
        />
      ))}
    </MapContainer>
  );
}

export default App;

If you do require thousands of markers, you may want to look at either using Leaflet directly (to see if it can give you some additional performance) or seeing if Google Maps or MapBox are better suited to your needs.

Conclusion

Leaflet and its React counterpart, React Leaflet, are a fantastic open source and free mapping alternative to Google Maps and MapBox, no API key required! It is an easy package to work with and one worth trying out.

Leaflet is an extremely light library, coming in at just under 40kb of JavaScript, and it is used by industry giants such as GitHub, Pinterest, and Etsy.

The source code shown in this article is available here.

Get setup with LogRocket's modern React error tracking in minutes:

  1. Visit https://logrocket.com/signup/ to get an app ID.
  2. Install LogRocket via NPM or script tag. LogRocket.init() must be called client-side, not server-side.
  3. $ npm i --save logrocket 

    // Code:

    import LogRocket from 'logrocket';
    LogRocket.init('app/id');
    Add to your HTML:

    <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
    <script>window.LogRocket && window.LogRocket.init('app/id');</script>
  4. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • ngrx middleware
    • Vuex plugin
Get started now
Leigh Halliday Leigh Halliday is a developer based out of Canada who works at FlipGive. He writes about React and Ruby on his blog and publishes React tutorials on YouTube.

15 Replies to “React Leaflet tutorial”

  1. Very cool. Might it be a better practice to import the Map component as another name such as LeafletMap though to avoid potential future conflict with the Map data-structure?

  2. thank you thank you very much. It helped me a lot. Especially to solve errors with the marker. Nothing I had seen had worked for me but I followed your simple steps and it was perfect everything worked

  3. I am having a problem with the open street maps mapm having missing tiles and being jumbled up.

    I have a npx create-react-app with the app.css containing that leaflet-container then in the app.js I have the code below wotih the ottawa centric map I added an import ./leaflet.css as well trying to fix my tiling problem but that then gives me an error of:

    Module not found: Can’t resolve ‘./images/layers-2x.png’ in ‘D:\code\ComIT\ReactJS\comit-poject-robm\src’

  4. you have to update the map center with new position, so on click of marker u can get the markes (lat,lng) and set the same value to the map center

  5. When i put Map component inside my App.js (even with the TileLyer as Children and center, zoom props) i don’t get anything on my screen even that grey screen which is supposed to be shown doesn’t. Thank you for any help

  6. Please i got this message in my console and the map isn’t display everywhere:

    Failed to find a valid digest in the ‘integrity’ attribute for resource ‘https://unpkg.com/[email protected]/dist/leaflet.css’ with computed SHA-256 integrity ‘SHMGCYmST46SoyGgo4YR/9AlK1vf3ff84Aq9yK4hdqM=’. The resource has been blocked.

  7. Thanks for this tutorials, i’ve finally win a way to complete it. Now please how can i make the Marker to move when i’m scrolling and how can i can a name of a place automatically just with latitude and longitude. Thanks

Leave a Reply