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. 🤗
▶️ CSS (You can find the styles in the repository at the end of this post)
🧵 Before you start coding ...
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.
🧵 Creating the component to display the map.
We will name the project: show-mapbox (optional, you can name it whatever you like).
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.
cd show-mapbox
Then we install the dependencies.
npm install
Then we open the project in a code editor (in my case VS code).
code .
🧵 First steps.
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.
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.
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.
🟠 Initializing MapBox.
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.
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.
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.
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.
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()'.
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.
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.
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.
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.
This is what the marker would look like on our map. 🥳
🧵 Adding a new marker on the map when double clicked.
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.