Skip to main content

Use Mapbox GL JS in a React app

Prerequisite
Familiarity with React and front-end development concepts.

This tutorial will walk you through adding a Mapbox GL JS map to a React project. You will learn how to write a custom React component to add an interactive map to a div. Once the map is created, you will learn how to respond to map interactions to update other parts of your app, and control the map based on events that happen outside of the map.

Note

Mapbox GL JS manipulates the DOM directly and does not export React components and hooks. This tutorial demonstrates direct use of the Mapbox GL JS API within a custom React component, which is our recommended implementation method.

For some use cases, a third-party wrapping library like react-map-gl may be useful.

You'll build a full-screen Mapbox GL JS web map along with some custom UI elements.

  • A panel that displays the map's center and zoom as the user moves the map.
  • A reset button that restores the original map view after the user has interacted with it.

You can try out the finished product in this browser preview panel:

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu

Getting started

You will need a few things in place before starting this tutorial:

  • A Mapbox access token. Your Mapbox access tokens are on your Account page.
  • A text editor. Use a text editor like Visual Studio Code to edit your code.
  • Node.js and npm. Follow the Downloading and installing Node.js and npm guide for instructions.
  • Familiarity with a terminal to execute node.js and npm commands.
  • Familiarity with React. You don't need to have a lot of experience using React to complete this tutorial, but you should be familiar with the underlying concepts and workflows.

Step 1: Create a React App with Vite

First, scaffold a React app using the popular build tool Vite.

  1. Run npm create vite@latest from your terminal and answer a few prompts. Give your project a name like my-map-app, choose React from the list of frameworks, then choose Javascript from the list of variants.

You will see a helpful message as your new app is created.

% npm create vite@latest
✔ Project name: … my-map-app
✔ Select a framework: › React
✔ Select a variant: › JavaScript

Scaffolding project in /Users/mapguru/my-map-app...

Done. Now run:

cd my-map-app
npm install
npm run dev

Follow the instructions to change directories, install dependencies (npm install) run the development server (npm run dev).

% npm run dev

> my-map-app@0.0.0 dev
> vite


VITE v5.4.4 ready in 111 ms

➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help

You will see some a message in your terminal saying the development server is available at http://localhost:5173/. Open this URL in your browser.

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu

Step 2: Create a map container element

Now that you have a React app scaffolded, let's update App.js to prepare for a map. Mapbox GL JS works by adding a map to an existing element, usually a div. This is referred to as the map container, and should be styled with CSS to position and size the map within the app.

In your code editor, replace the existing code in src/App.jsx with the snippet below. This adds an empty div with an id of map-container.

src/App.jsx
import './App.css'

function App() {
return (
<>
<div id='map-container' />
</>
)
}

export default App

Next, add CSS to make the root div full screen, and the map-container full height and width. (Some additional CSS rules are included to style elements that are added in later steps.) Since the map container div has no content, it will be invisible on the page. You can temporarily add a background-color rule to help see where it is positioned.

Replace the contents of src/App.css with the following snippet:

Warning

A common pitfall when working with Mapbox GL JS is adding a map to a container that has no height. Make sure your map container is styled properly before creating a new map.

src/App.css
#root {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

#map-container {
height: 100%;
width: 100%;
/* temporarily set the background color so we can tell where the map container is positioned */
background-color: lightgrey;
}

.sidebar {
background-color: rgb(35 55 75 / 90%);
color: #fff;
padding: 6px 12px;
font-family: monospace;
z-index: 1;
position: absolute;
top: 0;
left: 0;
margin: 12px;
border-radius: 4px;
}

.reset-button {
position: absolute;
top: 50px;
z-index: 1;
left: 12px;
padding: 4px 10px;
border-radius: 10px;
cursor: pointer;
}

Finally, delete the contents of src/index.css to its styles do not interfere with the map container's positioning.

src/index.css
/* index.css should be empty */

Save your changes across all files. The development server will hot reload with each saved change. If you see a light gray div filling the viewport, you have successfully positioned your map container for a full screen map.

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu

With your map container created and rendering in the app, you are ready to instantiate a Mapbox GL JS map.

Step 3: Add a Mapbox GL JS Map

First, install the mapbox-gl package using npm. See the installation guide for more information about installation options.

$ npm install mapbox-gl

At the top of App.jsx, import useRef and useEffect, from react, we will need these when setting up the map. You must also import mapbox-gl along with its associated CSS.

src/App.jsx
import { useRef, useEffect } from 'react'
import mapboxgl from 'mapbox-gl'

import 'mapbox-gl/dist/mapbox-gl.css';

import './App.css'
...

Next, add two refs using the useRef hook. The first ref will persist the map instance so you can control the map throughout the lifecycle of this component. The second ref exposes the map container's HTML element, and is used to tell Mapbox GL JS where to create the map.

Be sure to add the ref prop to your map container div.

src/App.jsx
function App() {

const mapRef = useRef()
const mapContainerRef = useRef()

...

return (
<>
<div id='map-container' ref={mapContainerRef}/>
</>
)
}

With the refs created, you are ready to instantiate the map. Follow these steps:

  1. Copy the code below and paste it between the refs, but above the return of the div.
  • This code adds a useEffect hook to App.jsx with an empty dependency array. This will run once when the component is first mounted and then calls new mapboxgl.Map() to add the map.
  1. Make sure to assign mapboxgl.accessToken to an access token from your account page. The access token is used for billing, and associates this map with your Mapbox account.

  2. Pass an options object into new mapboxgl.Map() to control the map. In this example, only the container option is specified as mapContainerRef.current. Mapbox GL JS will create the new map in your map container div.

new mapboxgl.Map() returns new instance of the Map class, and is assigned to mapRef.current so we can use it later.

src/App.jsx
function App() {
...
useEffect(() => {
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'
mapRef.current = new mapboxgl.Map({
container: mapContainerRef.current,
});

return () => {
mapRef.current.remove()
}
}, [])
...
}
Warning

Mapbox GL JS usage is billed by map loads. A map load is incurred each time this component mounts and the Map's load event fires. Depending on your app architecture and UX, you may want to persist the map component to avoid multiple map loads for a single user.

After these steps, your App.jsx should look like this:

src/App.jsx
import { useRef, useEffect } from 'react'
import mapboxgl from 'mapbox-gl'

import 'mapbox-gl/dist/mapbox-gl.css';

import './App.css'

function App() {

const mapRef = useRef()
const mapContainerRef = useRef()

useEffect(() => {
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'
mapRef.current = new mapboxgl.Map({
container: mapContainerRef.current,
});

return () => {
mapRef.current.remove()
}
}, [])

return (
<>
<div id='map-container' ref={mapContainerRef}/>
</>
)
}

export default App

Save your changes, make sure your development server is running and check the browser.

You will see a globe on a background of space and stars, the Mapbox logo, and an attribution notice. Hello, World!

You can use your scroll wheel to zoom in and out, and drag the globe to rotate it.

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu

You probably don't want to start your map zoomed all the way out like this, so you can add center and zoom options when calling new mapboxgl.Map().

Specify center as longitude and latitude coordinates in an array ([longitude, latitude]). zoom ranges from 0, zoomed all the way out to see the whole globe, to 22, zoomed in to street level.

src/App.jsx
...
mapRef.current = new mapboxgl.Map({
container: mapContainerRef.current,
center: [-74.0242, 40.6941],
zoom: 10.12
});
...

Here's what the map will look like zoomed into New York City:

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu
PLAYGROUND
Location Helper

For help finding coordinates for locations and appropriate zoom levels for the view you want, use our handy Location Helper tool.

With your new Mapbox GL JS map added, you are ready add code to integrate it into the rest of your app. We will cover integrations in both directions, updating app state in response to map events, and updating the map in response to events happening in the app.

Step 4: Respond to map events

There are several events generated by a Mapbox GL JS map which you can listen for to trigger functionality in your React app. In this step, we will listen for the events that occur when a user pans and zooms the map, and use them to update state in our React component.

For this example we will listen for the move event, which fires continuously with each change to the map's view, such as when the user drags or zooms the map. We can then query the map for its current center point coordinates and zoom level, and display these values in the app's UI.

Update App.jsx with the code highlighted below.

First, move the starting center point coordinates and zoom level to constants so they can be used for both initializing the map and as the initial state values.

Add two useState() hooks to store center, and zoom, using the constants defined above as their default values.

Update mapbox.Map() to use the center, and zoom state variables.

Next, add an event listener for the map's move event in the useEffect() hook. When move is fired, the callback for this listener queries the map using getCenter() and getZoom() and updates both state variables.

Finally, add a display div before the map container, adding placeholders lng, lat, and zoom. (This div is already styled using the CSS added in step 2). Use toFixed() to limit the number of decimal places for a cleaner display.

src/App.jsx
import { useRef, useEffect, useState } from 'react'
import mapboxgl from 'mapbox-gl'

import 'mapbox-gl/dist/mapbox-gl.css';
import './App.css'

 
const INITIAL_CENTER = [
 
-74.0242,
 
40.6941
 
]
 
const INITIAL_ZOOM = 10.12

function App() {
const mapRef = useRef()
const mapContainerRef = useRef()

 
const [center, setCenter] = useState(INITIAL_CENTER)
 
const [zoom, setZoom] = useState(INITIAL_ZOOM)

useEffect(() => {
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'
mapRef.current = new mapboxgl.Map({
container: mapContainerRef.current,
 
center: center,
 
zoom: zoom
});

 
mapRef.current.on('move', () => {
 
// get the current center coordinates and zoom level from the map
 
const mapCenter = mapRef.current.getCenter()
 
const mapZoom = mapRef.current.getZoom()
 

 
// update state
 
setCenter([ mapCenter.lng, mapCenter.lat ])
 
setZoom(mapZoom)
 
})

return () => {
mapRef.current.remove()
}
}, [])

return (
<>
 
<div className="sidebar">
 
Longitude: {center[0].toFixed(4)} | Latitude: {center[1].toFixed(4)} | Zoom: {zoom.toFixed(2)}
 
</div>
<div id='map-container' ref={mapContainerRef} />
</>
)
}

export default App

As you drag and zoom the map, your new event listener fires, updating the state and triggering a component render. Try it out here:

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu

Now that you know how to respond to map events, you can move on to controlling the map using external events.

Step 5: Control the Map from external events

In this step, you'll add a button to your app's UI and trigger a response from the map when it is clicked. More specifically, you'll add a Reset button which will restore the map's view to its original location after the user moves and pans the map.

Add a <button> before the map container div and set its onClick prop to handleButtonClick. (This button is already styled using the CSS added in step 2)

Next, create a new function handleButtonClick and call the map's flyTo() method, specifying the center and zoom to animate the map's camera to using the constants defined in step 4.

Save your work and check your browser. Move the map by dragging and zooming, then click the Reset button. The map will smoothly transition back to the original center point and zoom.

Notice that the move event is fired continuously as flyTo() animates the map, so there is no need for additional code to update the center, and zoom UI after calling flyTo()

You can expand the hidden sections in this code snippet to see the final code for App.jsx.

src/App.jsx
 
const handleButtonClick = () => {
 
mapRef.current.flyTo({
 
center: INITIAL_CENTER,
 
zoom: INITIAL_ZOOM
 
})
 
}
 

 

 
return (
 
<>
 
<div className="sidebar">
 
Longitude: {center[0].toFixed(4)} | Latitude: {center[1].toFixed(4)} | Zoom: {zoom.toFixed(2)}
 
</div>
 
<button className='reset-button' onClick={handleButtonClick}>
 
Reset
 
</button>
 
<div id='map-container' ref={mapContainerRef} />
 
</>
 
)

Move the map and click the Reset button. handleButtonClick fires and restores the original camera position.

circlecirclecircle
arrow-leftarrow-rightrotate
heartmapboxmenu

Congratulations on completing this Mapbox tutorial. You've learned the basics of setting up a Mapbox GL JS map in a React app:

  • Instantiating a Mapbox GL JS Map in a useEffect hook
  • Listening for map events and updating app state
  • Listening for events in the app and controlling the map

Next Steps

Here are some ideas for improving your new component that you can try on your own:

  • Only enable/show the Reset button when the user has already dragged the map or changed the zoom level.
  • Specify a different map style.
  • Slow down the flyTo duration.
  • Specify additional options when creating the map.
  • Add more buttons that fly the map to locations of interest.

Additional Resources

Be sure to browse the Mapbox GL JS examples, most of which include React code snippets to show various techniques and best practices.

You can also check our full-featured demo applications with source available code built with React:

Was this page helpful?