Using Mapbox in React: A Comprehensive Guide

PRIYAM MONDAL
6 min readMay 22, 2023

--

Mapbox is a powerful mapping platform that allows developers to integrate interactive and customizable maps into their web applications. In this article, we will explore how to use Mapbox in a React application to display maps, add markers, calculate directions, and more. We will provide step-by-step instructions and code snippets to help you get started with Mapbox in React.

Getting Started

To begin, make sure you have a React application set up. If you don’t have one already, you can create a new React project using create vite@latest or any other preferred method.

Installing Dependencies

To use Mapbox in React, we need to install several dependencies. Open your terminal and navigate to your React project directory. Then run the following command:

npm install mapbox-gl axios @mapbox/mapbox-sdk react-icons bootstrap sass

This will install the necessary packages: mapbox-gl for the Mapbox library, axios for making HTTP requests, and @mapbox/mapbox-sdk for geocoding services.

Setting Up Mapbox Access Token

To access Mapbox’s APIs, you need to have an access token. If you don’t have one, you can sign up for a Mapbox account at mapbox.com. Once you have an account, navigate to your account settings and create a new access token.

Copy your access token, as we’ll need it later in our code. It’s recommended to store the access token securely, such as in an environment variable, for production applications. For this article, we’ll assume you have the access token stored in a safe location.

Importing Dependencies:

import React, { useRef, useEffect, useState, useContext } from "react";
import "./Map.scss";
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import axios from "axios";
import MapboxGeocoding from "@mapbox/mapbox-sdk/services/geocoding";
import { FaArrowsAltV } from "react-icons/fa";
import { MdLocationOn } from "react-icons/md";
import { AiFillHome } from "react-icons/ai";

Here, the necessary dependencies are imported for creating the map and implementing the geocoding functionality.

Initializing State and Refs:

const mapContainerRef = useRef(null);
const [mapStyle, setMapStyle] = useState("mapbox://styles/mapbox/streets-v11");
const [isFocused, setIsFocused] = useState(false);
const [origin, setOrigin] = useState("");
const destination = [88.3639, 22.5726];
const [routeGeometry, setRouteGeometry] = useState(null);
const [originCord, setOriginCord] = useState([]);
let originCoordinates = [];
const [routeInfo, setRouteInfo] = useState([]);
const [suggestions, setSuggestions] = useState([]);
const [expand, setExpand] = useState(false);
const initialItemCount = 4;
const geocodingClient = MapboxGeocoding({
accessToken: import.meta.env.VITE_MAP_BOX_ACCESS_TOKEN,
});

Here, the state variables are declared to keep track of various data such as map style, focus state, origin address, destination coordinates, route geometry, origin coordinates, route information, suggestions for autocomplete and expand state for directions. Refs are used to reference the map container.

Map Initialization and Styling:

This section initializes the Mapbox map by creating a new mapboxgl.Map instance and setting its initial properties such as the container, style, center coordinates, and zoom level.

It also adds the compass control and a custom marker to the map.

useEffect(() => {
mapboxgl.accessToken = import.meta.env.VITE_MAP_BOX_ACCESS_TOKEN;
const map = new mapboxgl.Map({
container: mapContainerRef.current,
style: mapStyle,
center: [88.3639, 22.5726], // longitude and latitude
zoom: 12,
attributionControl: false,
});

map.on("style.load", () => {
// Add the compass control
const compassControl = new mapboxgl.NavigationControl({
showCompass: true,
});
map.addControl(compassControl, "top-right");

// Create a marker with a custom icon
const marker = new mapboxgl.Marker({
element: document.getElementById("custom-marker"),
})
.setLngLat([88.3639, 22.5726]) // longitude and latitude
.addTo(map)
.setPopup(
new mapboxgl.Popup({ closeButton: true }).setHTML(
`
<div class="location-details">
<span><strong>City:</strong> Kolkata</span><br>
<span><strong>State:</strong> West Bengal</span><br>
<span><strong>Country:</strong> INDIA</span></div>
</div>
`
)
);

// Create a marker at the starting position
const startMarker = new mapboxgl.Marker()
.setLngLat(originCord)
.addTo(map);

if (routeGeometry) {
map.addSource("route", {
type: "geojson",
data: {
type: "Feature",
geometry: routeGeometry,
},
});

map.addLayer({
id: "route",
type: "line",
source: "route",
layout: {
"line-join": "round",
"line-cap": "round",
},
paint: {
"line-color": "#3b9ddd",
"line-width": 6,
},
});
}
// Get the route bounds
const bounds = routeGeometry.coordinates.reduce(
(bounds, coord) => bounds.extend(coord),
new mapboxgl.LngLatBounds()
);

// Zoom out to fit the route within the map view
map.fitBounds(bounds, {
padding: 50,
});
});

// return () => {
// map.remove();
// };
}, [mapStyle, routeGeometry]);

Autocomplete Search Input:

This section handles the user input in the search input field (<input id="fromAddress" />) and fetches autocomplete suggestions using the Mapbox Geocoding API.

const handleInputChange = (event) => {
const { value } = event.target;
setOrigin(value);

axios
.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/${value}.json`, {
params: {
access_token: mapboxgl.accessToken,
autocomplete: true,
types: ["place"],
limit: 5,
},
})
.then((response) => {
const { features } = response.data;
setSuggestions(features);
})
.catch((error) => {
console.error("Error fetching autocomplete suggestions:", error);
});
};

Handling Autocomplete Suggestions:

This section displays the autocomplete suggestions based on user input and handles the selection of a suggestion.

const handleSelectSuggestion = (suggestion) => {
setOrigin(suggestion.place_name);
setOriginCord(suggestion.center);
setSuggestions([]);
};

Calculating and Displaying Directions:

This section calculates the route directions using the Mapbox Directions API based on the origin and destination coordinates.

It makes an HTTP request to the API and retrieves the route information, including distance, duration, and step-by-step instructions.

const calcRouteDirection = async () => {
if (origin.length > 2) {
try {
const origin = document.getElementById("fromAddress").value;
if (origin.length > 2) {
try {
const response = await geocodingClient
.forwardGeocode({
query: origin,
types: ["place"],
limit: 1,
})
.send();

const destinationCoordinates = response.body.features[0].center;
originCoordinates = destinationCoordinates;
setOriginCord(destinationCoordinates);
} catch (error) {
console.error("Error calculating directions:", error);
throw error;
}
}
const response = await axios.get(
`https://api.mapbox.com/directions/v5/mapbox/${localStorage.getItem(
"mode"
)}/${originCoordinates[0]},${originCoordinates[1]};${
destination[0]
},${destination[1]}?steps=true&geometries=geojson&access_token=${
import.meta.env.VITE_MAP_BOX_ACCESS_TOKEN
}`
);

const routes = response.data.routes;
console.log("routes=>", routes);
setRouteInfo(routes);
// Check if any routes are returned
if (routes.length > 0) {
const { distance, duration, geometry } = routes[0];

// Valid directions, use the distance and duration for further processing
const directions = {
distance,
duration,
};
localStorage.setItem("fromLocation", origin);
setRouteGeometry(geometry); // Set the route geometry
return directions;
} else {
// No routes found
throw new Error("Unable to calculate directions");
}
} catch (error) {
// Handle error
console.error("Error calculating directions:", error);
throw error;
}
}
};

Switching Travel Modes:

This section allows the user to switch between different travel modes (driving, walking, cycling) and recalculate the directions accordingly.

It updates the localStorage value for the selected travel mode and triggers the calcRouteDirection function.

<a
className={`mode-button ${selectedMode === "driving" ? "active" : ""}`}
onClick={() => handleModeChange("driving")}
>
Driving
</a>
<a
className={`mode-button ${selectedMode === "walking" ? "active" : ""}`}
onClick={() => handleModeChange("walking")}
>
Walking
</a>
<a
className={`mode-button ${selectedMode === "cycling" ? "active" : ""}`}
onClick={() => handleModeChange("cycling")}
>
Cycling
</a>
const handleModeChange = (mode) => {
localStorage.setItem("selectedMode", mode);
setSelectedMode(mode);
calcRouteDirection();
};

Displaying Route Instructions:

This section displays the step-by-step instructions for the selected route. It iterates over the routeInfo array and renders each instruction as a list item.

<ul>
{routeInfo[0].legs[0].steps.map((step, index) => (
<li key={index}>{step.maneuver.instruction}</li>
))}
</ul>

Updating Map and Route Visualization:

This section updates the map and visualizes the route on the map whenever the origin, destination, or selected travel mode changes.

It updates the map’s center and zoom level based on the route bounds and displays the route using a Mapbox LineString and Layer.

useEffect(() => {
if (map && routeGeometry) {
map.fitBounds(routeGeometry.bounds, { padding: 20 });

map.addLayer({
id: "route",
type: "line",
source: {
type: "geojson",
data: {
type: "Feature",
properties: {},
geometry: routeGeometry,
},
},
layout: {
"line-join": "round",
"line-cap": "round",
},
paint: {
"line-color": "#00f",
"line-width": 4,
},
});
}
}, [map, routeGeometry]);

In conclusion, the code presented demonstrates the potential of React, Mapbox, and APIs in creating a dynamic and functional map and directions component. It showcases features such as autocomplete suggestions, travel mode switching, route calculation, route visualization, and step-by-step directions. By leveraging these technologies, developers can build powerful and user-friendly mapping applications that enhance the user experience and provide valuable navigation functionality.

--

--

PRIYAM MONDAL

Full Stack Developer | React.js | Express.js | MongoDB | MySQL | Node.js