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.
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.
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='© <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:
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> ); }
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>
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.
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='© <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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ 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>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]
15 Replies to "React Leaflet tutorial"
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?
I think you’re probably right, Joshua! Good suggestion 🙂
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
Nice! I’m glad it was able to help. I have an article that talks about clustering markers in React Leaflet if that is something you need: https://www.leighhalliday.com/leaflet-clustering
hi , is there away to recenter the map on the clicked marker
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’
I am looking for same
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
Thanks!! It was so useful for me!
can you implement routes or directions with react leaflet?
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
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.
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
https://github.com/LucaschS/ClimbingApp
My app doesnt work can anybody help me
You should avoid react-leaflet. If you need to render thousands of objects, you perhaps come across performance issues https://twitter.com/Andrej_Gajdos/status/1661023641432358917