VOOZH about

URL: https://blog.logrocket.com/react-leaflet-tutorial/

⇱ React Leaflet tutorial - LogRocket Blog


2021-10-22
1381
#react
Leigh Halliday
11896
👁 Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

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

👁 React Leaflet Tutorial

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.

🚀 Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

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.


Over 200k developers use LogRocket to create better digital experiences

👁 Image
Learn more →

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 set up 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

    $ 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>
     
  3. (Optional) Install plugins for deeper integrations with your stack:
    • Redux middleware
    • NgRx middleware
    • Vuex plugin
Get started now
👁 Image
👁 Image
👁 Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

Debug Next.js apps with AI agents and next-browser

Learn how next-browser gives AI agents runtime context for debugging Next.js apps, including React props, hydration, PPR, forms, and performance.

👁 Image
Emmanuel John
Jun 17, 2026 ⋅ 9 min read

Stop hardcoding LLM SDKs: Dynamic LLM routing with OpenRouter and Next.js

Build dynamic LLM routing in Next.js with OpenRouter, TanStack AI, task classification, model fallbacks, and cost-aware routing.

👁 Image
Chizaram Ken
Jun 16, 2026 ⋅ 13 min read

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension — no new framework required.

👁 Image
Ikeh Akinyemi
Jun 12, 2026 ⋅ 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo — with email/password login, Google OAuth, session persistence, and protected routes.

👁 Image
Chinwike Maduabuchi
Jun 9, 2026 ⋅ 13 min read
View all posts

Would you be interested in joining LogRocket's developer community?

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 now