React-Leaflet 101: Bringing Interactive Maps to Your React Projects

Jakob Madsen
Sopra Steria Norge
Published in
5 min readMar 30, 2023

I’m a big fan of visualizing data — whether it’s tracking temps, timestamps, or even plotting locations on a map. I’ve been playing around with Leaflet in my latest hobby project, and since I like using React, I thought I’d give React-Leaflet a go.

In this guide I am going to be using my project as a reference, you can check the page out here. The concept for the page is really simple, it’s just a collection of food reviews tied to a store with a location. The location is shown on the map. The main map functionalities I am using here is flyTo , and a library called Leaflet.SmoothMarkerBouncing for added animations to the selected marker.

To preface this guide I want to make it clear that the React structure is not the best practice and only serves to have a testbed for the other technologies I wanted to learn in the project.

For brevity, I am not including some parts of the code, such as fetching data and login, as that is outside of the scope of this guide.

Package versions used in this article include:

    "react": "^18.2.0",
"react-scripts": "5.0.1",
"react-dom": "^18.2.0",
"react-leaflet": "^4.2.0",
"leaflet": "^1.9.3",
"leaflet.smooth_marker_bouncing": "^3.0.2",

Starting in our App.tsx I import the MapContainer and other Leaflet components I need as shown in the code below. We are also using a custom MapController since any reference to useMap and the Leaflet context needs to be in a component that is a descendant of the map.

My data is in the reviewData object, where each entry has a property called location . The location data follows the L.LatLng format specified by Leaflet, here.

import React, { useState } from "react";
import { MapContainer, TileLayer, Marker} from "react-leaflet";
import { MapController } from "./components/map/MapController";

function App() {
// my data which includes the location
const [reviewData, setReviewData] = useState<Review[]>([]);
const [selectedReview, selectReview] = useState<Review | null>();

return (
<div>
{/* other components ... */}

<MapContainer
id="map"
// where the map should start, this is for Oslo
center={[59.914, 10.734]}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>

<MapController
selectedReview={selectedReview}
/>

{reviewData.map((review) => {
return (
<Marker
position={review.location}
eventHandlers={{
click: () => {selectReview(review)},
}}
></Marker>
);
})}
</MapContainer>
</div>
);
}

export default App;

If you want to disable the user controls for the map you can disable the following props on MapContainer : scrollWheelZoom, dragging, keyboard, zoomControl, and doubleClickZoom.

Also note that the TileLayer in the code is using the default set, while my page is using outdoors-v9 from Mapbox.

So when we have the basic React-Leaflet setup we can start making our MapController , as mentioned above to access the Leaflet context we need to be in a descendant of the map.

We are going to start by adding the simple functionality of just flying to wherever the chosen review is. To access the map we are using the useMap hook imported from react-leaflet . The simplest possible way is just using an useEffect to check the selected review and fly to its location or to the specified “center” when there is no selected review.

import React, { FC, useEffect } from "react";
import { useMap } from "react-leaflet";

const MapController: FC<{ selectedReview: Review }> = ({selectedReview}) => {
const map = useMap();
const flyToDuration = 1.5;

const flyTo = (location: LatLngTuple) => {
map.flyTo(location, 15, {
animate: true,
duration: flyToDuration,
});
};

const flyToCenter = () => {
map.flyTo([59.914, 10.734], 13, {
animate: true,
duration: flyToDuration,
});
};

useEffect(() => {
if(selectedReview) {
flyTo(selectedReview.location);
} else {
flyToCenter();
}
}, [selectedReview])

return null;
});

export { MapController };

Note we are also setting the zoom level to 15 when looking at a review and 13 when looking at the “center”.

The functionality described above should look something like this, minus the bouncing animation which we will be adding soon.

To add the bouncing animation we need to install Leaflet.SmoothMarkerBouncing by running the command and importing it.

npm install leaflet.smooth_marker_bouncing

The documentation is only for vanilla JS but we can use it with React-Leaflet by swapping the L with a useMap context. The code below is a shortened version of MapController that just shows the parts we need for the animation.

const MapController: FC<{ selectedReview: Review }> = ({selectedReview}) => {
const map = useMap();
const flyToDuration = 1.5;

// [ flyTo code... ]

useEffect(() => {
map.eachLayer(async (layer) => {
if (layer instanceof L.Marker) {
if (layer.position == props.selectedReview.location) {
await sleep(flyToDuration * 1000 + 100);
layer.bounce();
} else {
layer.stopBouncing();
}
}
});
}, [props.selectedReview]);
});

If you are using Typescript you will most likely need to ignore the .bounce() and .stopBouncing() methods as they do not exist on L.Marker . To select the correct layer I check if their position is equal to the review location, this will also bounce multiple layers if they are overlapping. Another thing I do here is waiting for the flyTo animation to end before bouncing, this is done with a custom sleep promise and a constant flyToDuration time.

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

To sum up, React-Leaflet is a powerful tool for creating interactive maps in React projects. This guide covered the basic setup, MapContainer component, and a custom MapController component, as well as adding an animation library. There is a lot of fun to be had with Leaflet so don’t be afraid to experiment!

--

--