Showing MapBox map with React. 🗺️
Tags: webdev, javascript, react, typescript
Published: 2022-07-01 13:39:40

The purpose of this post is to teach how to use the MapBox GL JS library to display interactive maps in React JS applications.

In this case we are going to display a map, and add an event to it, which is executed at the moment of double clicking a marker is placed at that position that was just double clicked.

> 🚨 **Note**: This post requires you to know the basics of **React with TypeScript (basic hooks and fetch requests)**.
>

Any kind of Feedback or improvement is welcome, thanks and hope you enjoy the article. 🤗

---

## Table of contents.

> 📌 [Technologies to be used...](#1)   
> 📌 [Before you start coding...](#2)   
> 📌 [Creating the project.](#3)   
> 📌 [First steps.](#3)   
> 📌 [Creating the component to display the map.](#5)   
> 📌 [Showing the map on screen.](#6)   
>> 📍 [Keeping the reference to the map container.](#7)   
>>> 🔴 [Why do we need to keep the reference?](#8)   
>>
>> 📍 [Initializing MapBox.](#9)   
>
> 📌 [Adding a marker at the initial position.](#10)   
>> 📍 [Before our component grows.](#11)  
>> 📍 [Listening for the 'load' event in the map.](#12)
>>> 🔴 [Creating the function to add markers.](#13)
>>
>> 📍 [Showing the marker.](#14)
>
> 📌 [Adding a new marker on the map when double clicked.](#15)  
> 📌 [Conclusion.](#16)  
> 📌 [Source code.](#17)

&nbsp;

## 🧵 Technologies to be used. <a id="1"></a>

- ▶️ React JS (v.18)
- ▶️ Vite JS
- ▶️ TypeScript
- ▶️ [MapBox](https://docs.mapbox.com/mapbox-gl-js/guides)
- ▶️ CSS (You can find the styles in the repository at the end of this post) 

&nbsp;

## 🧵 Before you start coding ... <a id="2"></a>

Before we start working with the code we have to do a couple of things to be able to use the MapBox map.  

1- You have to create a MapBox account.  
2- In your account you will look for the **access token** that MapBox creates by default or if you prefer, you can create a new access token.
3- Save this access token to use it later.

&nbsp;

## 🧵 Creating the component to display the map. <a id="3"></a>

We will name the project: **`show-mapbox`** (optional, you can name it whatever you like).
```bash
npm init vite@latest
```
We create the project with Vite JS and select React with TypeScript.  
Then we execute the following command to navigate to the directory just created.

```bash
cd show-mapbox
```
Then we install the dependencies.
```bash
npm install
```
Then we open the project in a code editor (in my case VS code).
```bash
code .
```

&nbsp;

## 🧵 First steps. <a id="4"></a>
We need to install MapBox in our application:
```
npm i mapbox-gl
```
And since we are using TypeScript, we need to install the MapBox types:
```
npm i -D @types/mapbox-gl
```

Inside the folder **`src/App.tsx`** we delete all the content of the file and place a **h1** that says *"Hello world "* in the meantime.

```js
const App = () => {
  return (
    <div>
        <h1>Hello World</h1>
    </div>
  )
}
export default App
```

> 🚨 **Note**: It is necessary to place the MapBox styles so that when we are using the map, it looks the best way.
>
The best will be to import the styles in the **src/main.tsx** file, at the top of our application.
Line to place:
```
import 'mapbox-gl/dist/mapbox-gl.css'
```
This is what the **src/main.tsx** file would look like 
```js
import React from 'react';
import ReactDOM from 'react-dom/client';

import App from './App';

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

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)
```

&nbsp;

## 🧵 Creating the component to display the map. <a id="5"></a>

We create the **src/components** folder and create the **MapView.tsx** file.
And the only thing we need to display the map is a **div** tag.

> 🚨 **Note**: Before we start displaying the map; this component must be given styles, a height and a width so that we can then display the map correctly.
>

```js
export const MapView = () => {
    return (
        <div className='map' />
    )
}
```

&nbsp;

## 🧵 Showing the map on screen. <a id="6"></a>

To display the map we will need to use 2 hooks.
The first one will be the useRef. We need useRef to store the reference of the div where the map will be rendered.

The other hook is the useEffect hook. We will use this hook to initialize the map.

&nbsp;

### 🟠 **Keeping the reference to the map container.** <a id="7"></a>

We use the hook useRef for this task, as follows:

```js
import { useRef } from 'react';

export const MapView = () => {

    const mapRef = useRef<HTMLDivElement>(null);

    return <div ref={mapRef} className='map' />
}
```

&nbsp;

#### 🔴 **Why do we need to keep the reference?** <a id="8"></a>

Bueno, podríamos solo colocar solo un **ID** al **div** y ya con eso funcionaria. 😌

El problema sera cuando queramos usar mas de un mapa. 🤔

Si usamos más de un componente **MapView**, solo se renderizaría un solo mapa por que tienen el mismo ID; y para evitar eso, usamos el hook useRef, ya que cada vez que reutilizamos el componente MapView se creara una nueva referencia.

&nbsp;

### 🟠 **Initializing MapBox.** <a id="9"></a>

We create the **src/utils** folder and create a new file called **initMap.ts** and there we will build the function to initialize the map.

This function has to receive: 
- **container**: HTML element, in this case the **div**, where the map will be rendered.

- **coords**: coordinates of the place. They have to be of type array of two numbers, where the first position is the **longitude** and the second position is the **latitude**.

``` js
import { Map } from 'mapbox-gl';

export const initMap = (container: HTMLDivElement, coords: [number, number]) => {
    
}
```

Inside the function we are going to return a new instance of Map.   
We return it because we are going to need that instance to make more events and actions. In the case that you only need to show the map and already, it won't be necessary to return anything.

``` js
import { Map } from 'mapbox-gl';

export const initMap = (container: HTMLDivElement, coords: [number, number]) => {
    
    return new Map();
}
```
The **Map** class requires certain options.

- **container**: the HTML element where the map will be rendered, its value will be the container that comes to us by parameter of the function.

- **style**: type of style of the map, in this case I will use the dark, [in the MapBox documentation there are more styles](https://docs.mapbox.com/api/maps/styles/#mapbox-styles).

- **pitchWithRotate**: is the tilt control of the map, in this case we want to remove it, so we put **false**.

- **center**: are the coordinates where the map will be positioned when initialized, its value will be the **coords** that comes to us by parameter of the function.

- **zoom**: the initial zoom of the map, the levels go from 0 to 22.

- **accessToken**: the token that we saved previously. So I recommend you to save this token in an environment variable and use this variable in this **accessToken** property.

- **doubleClickZoom**: action that is triggered when double clicking by default is to increase the zoom, but we will set it to false, since we will use the action of the double click for another task.

And that would be our function ready to use. 😌

``` js
import { Map } from 'mapbox-gl';

export const initMap = (container: HTMLDivElement, coords: [number, number]) => {
    
    return new Map({
        container,
        style: 'mapbox://styles/mapbox/dark-v10',
        pitchWithRotate: false,
        center: coords,
        zoom: 15,
        accessToken: import.meta.env.VITE_KEY as string,
        doubleClickZoom: false
    });
    
}
```

Now in our **MapView** component we will use the useEffect to call the function that we have created. 

Inside the useEffect we will make a condition, where only if the value of useRef exists, we will initialize our map.

In the initMap function, we send the HTML element that is in the ***current*** property of **mapRef**,
then we send the coordinates ( **[longitude, latitude]** ).

```js
import { useRef } from 'react';;
import { useMap } from '../hook/useMap';

export const MapView = () => {

    const mapRef = useRef<HTMLDivElement>(null);
    
    useEffect(() => {
        if (mapRef.current) {
            initMap(
                mapRef.current,
                [-100.31019063199852, 25.66901932031443]
            )
        }
    }, []);

    return (
        <div ref={mapRef} className='map' />
    )
}
```
Now, we would see the map on screen 🥳, as in this image:

![map](https://res.cloudinary.com/dnxchppfm/image/upload/v1656518892/posts/show-map/01_nqaxbh.png)

Well, what now?
How about we add some events to add bookmarks 😉.

&nbsp;


## 🧵 Adding a marker at the initial position. <a id="10"></a>

Before creating events with the map, we have to keep the reference to the Map instance, for that we will use again **useRef**.

We create a new reference called **mapInitRef** which will be of type map or null.

The initMap function returns the Map instance, so we will assign this instance to mapInitRef.

```js
const mapInitRef = useRef<Map | null>(null);

useEffect(() => {
    if (mapRef.current) {

        mapInitRef.current = initMap(
            mapRef.current,
            [-100.31019063199852, 25.66901932031443]
        );

    }
}, []);
```

&nbsp;

### 🟠 **Before our component grows...** <a id="11"></a>
At this point, it will be better to refactor our code, creating a custom hook to handle the map logic and leave our **MapView** component clean.

We create the **src/hooks** folder and inside we create the **useMap.ts** file and move the MapView logic to the **useMap.ts** file.

This custom hook receives as parameter the container where the map will be rendered. 

Now, we replace the word **mapRef** by **container**.

```js
import { useEffect, useRef } from 'react';
import { Map } from 'mapbox-gl';
import { initMap } from '../utils/initMap';

export const useMap = (container: React.RefObject<HTMLDivElement>) => {

    const mapInitRef = useRef<Map | null>(null);

    useEffect(() => {
        if (container.current) {

            mapInitRef.current = initMap(
                container.current,
                [-100.31019063199852, 25.66901932031443]
            );

        }
    }, []);
}
```

Then we make the call of the hook in our component **MapView**. 

And thus we will have our component, much more readable. 😉

```js
import { useRef } from 'react';;
import { useMap } from '../hook/useMap';

export const MapView = () => {

    const mapRef = useRef<HTMLDivElement>(null);
    useMap(mapRef)

    return <div ref={mapRef} className='map' />
}
```

&nbsp;

### 🟠 **Listening for the 'load' event in the map.** <a id="12"></a>

Well, so far we already have the reference to the map instance available.

Now what we want to do is that when we load the map, a marker is displayed on the screen.

For this, the Map instance has the method **'on'** that allows us to listen to certain events that are triggered in the map. 

So, first we create a useEffect.

```js
useEffect(() => {

}, [])
```
Then, we are going to make an evaluation where if the mapInitRef.current exists (that is to say that it has the value of the instance), 
we execute the following event **'on()'**.

```js
useEffect(() => {
    
    mapInitRef.current && mapInitRef.current.on();

}, [])
```

The on method in this case receives 2 parameters:
- **type**: the action to listen to, in this case it will be the load action, since we want something to be executed when the map has already been loaded.
- **listener**: the function to execute when the action is listened. 

```js
useEffect(() => {
    
    mapInitRef.current && mapInitRef.current.on(
        'load', 
        () => {}
    )

}, [])
```


#### 🔴 **Creating the function to add markers.** <a id="13"></a>

Now let's create a function to add markers to the map.

Inside the folder **src/utils** we create the file **generateNewMarker.ts** and add a new function.

This function receives as parameter:
- **lat**: latitude.
- **lng**: longitude.
- **map**: the map to add the marker to.

```js
import { Map } from 'mapbox-gl';

export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {

}
```

To create a marker we create a new instance of the Marker class, which we send certain optional parameters:
- **color**: color of the marker.
- **scale**: size of the marker. 

```js
import { Popup, Marker, Map } from 'mapbox-gl';

export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {

    new Marker({ color: '#63df29', scale: 1.5 })
}
```
Then, we execute the **setLngLat** method to send it the longitude and latitude as an array to tell the marker where it should be placed.

```js
import { Popup, Marker, Map } from 'mapbox-gl';

export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {

    new Marker({ color: '#63df29', scale: 1.5 })
        .setLngLat([lng, lat])
}
```

And finally we call the **addTo** method to add it to the map, we pass it the instance of the map that we received by parameter.

```js
import { Popup, Marker, Map } from 'mapbox-gl';

export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {

    new Marker({ color: '#63df29', scale: 1.5 })
        .setLngLat([lng, lat])
        .addTo(map)
}
```
An extra, would be to create a PopUp. For it we make a new instance of the class **Popup** (we save it in a constant), which we send certain parameters that are optional:
- **closeButton**: show the close button, we set it to false.

- **anchor**: the position where the PopUp should be shown in the marker.

```js
import { Popup, Marker, Map } from 'mapbox-gl';

export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {

    const popUp = new Popup({ closeButton: false, anchor: 'left', })

    new Marker({ color: '#63df29', scale: 1.5 })
        .setLngLat([lng, lat])
        .addTo(map)
}
```

And to place custom content to the **PopUp**, we will call the **setHTML** method and send it HTML as a string.

```js
import { Popup, Marker, Map } from 'mapbox-gl';

export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {

    const popUp = new Popup({ closeButton: false, anchor: 'left', })
        .setHTML(`<div class="popup">You click here: <br/>[${lng},  ${lat}]</div>`)

    new Marker({ color: '#63df29', scale: 1.5 })
        .setLngLat([lng, lat])
        .addTo(map)
}
```

Finally, to the instance of the **Marker**, before the addTo method, we place the **setPopup** method and send it the **popUp** constant.

```js
import { Popup, Marker, Map } from 'mapbox-gl';

export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {

    const popUp = new Popup({ closeButton: false, anchor: 'left', })
        .setHTML(`<div class="popup">You click here: <br/>[${lng},  ${lat}]</div>`)

    new Marker({ color: '#63df29', scale: 1.5 })
        .setLngLat([lng, lat])
        .setPopup(popUp)
        .addTo(map)
}
```
It's time to call this method! 😉

&nbsp;

### 🟠 **Showing the marker.** <a id="14"></a>
In our hook useMap, inside the useEffect where we were creating adding the event to listen to the map when it loads for the first time, we call the **generateNewMarker** method.

```js
useEffect(() => {
    mapInitRef.current && mapInitRef.current.on(
        'load', 
        () => generateNewMarker()
}, [])
```
To this method we send an object containing: 
- map: we send **mapInitRef.current** since it is the instance of the map.
- the second parameter we send **mapInitRef.current!.getCenter()**.  
This function returns an array of two numbers that are the longitude and latitude (these numbers are those that we passed at the beginning, at the time of initializing the map), for which we spread them with the spread operator.

```js
useEffect(() => {
    mapInitRef.current && mapInitRef.current.on(
        'load', 
        () => generateNewMarker({ 
            map: mapInitRef.current!, 
            ...mapInitRef.current!.getCenter() 
        })
}, [])
```

Finally, it is good practice that when we are listening to events within a useEffect, when the component is disassembled (which in this case will not happen because we only have one view which is the map), it is necessary to stop listening to the event and not execute anything.

```js
useEffect(() => {
    mapInitRef.current && mapInitRef.current.on(
        'load', 
        () => generateNewMarker({ 
            map: mapInitRef.current!, 
            ...mapInitRef.current!.getCenter() 
        })

    return () => { 
        mapInitRef.current?.off('load', generateNewMarker) 
    }
}, [])
```

&nbsp;

This is what the marker would look like on our map. 🥳 

![initial marker](https://res.cloudinary.com/dnxchppfm/image/upload/v1656518893/posts/show-map/02_rubihe.png)

&nbsp;


## 🧵 Adding a new marker on the map when double clicked. <a id="15"></a>
This will be very simple, since we have almost everything done.
It is only necessary, to add a new effect in our custom hook.

And following the same practices as when we listened to the **'load'** event before.

- We validate that mapInitRef contains the map instance.

- We call the on method to listen for the **'dblclick'** event.

- Now, the listener that is executed gives us access to the longitude and latitude (which come as an array of two numbers), which we can unstructure from the listener.

- We execute the function **generateNewMarker**.

- To the function generateNewMarker we send the map, which will have the value of the map instance found in **mapInitRef.current**. Then, we spread the value of **lngLat** given to us by the listener.

- We clean the effect with the return, stopping listening to the **'dblclick'** event.


```js
useEffect(() => {
    
    mapInitRef.current && mapInitRef.current.on(
        'dblclick', 
        ({ lngLat }) => generateNewMarker({ 
            map: mapInitRef.current!, 
            ...lngLat 
        }))

    return () => { 
        mapInitRef.current?.off('dblclick', generateNewMarker) 
    }

}, [])
```

&nbsp;

This is how the markers would look on our map. 🥳 

![markers](https://res.cloudinary.com/dnxchppfm/image/upload/v1656518892/posts/show-map/03_riluwc.png)

&nbsp;

## 🧵 Conclusion. <a id="16"></a>
The whole process I just showed, is one of the ways in which displaying a map with React JS can be done. 🗺️

I hope I helped you understand how to perform this exercise,thank you very much for making it this far! 🤗  

I invite you to comment if you know any other different or better way how to make display a map with React JS. 🙌

And if you liked the content, don't forget to support me by reacting to this post or sharing this post with someone who cares! ❤️

&nbsp;

### 🧵 **Source code.** <a id="17"></a>
{%embed https://github.com/Franklin361/show-map %}