DEV Community

Cover image for Showing MapBox map with React. ๐Ÿ—บ๏ธ
Franklin Martinez
Franklin Martinez

Posted on

Showing MapBox map with React. ๐Ÿ—บ๏ธ

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...

๐Ÿ“Œ Before you start coding...

๐Ÿ“Œ Creating the project.

๐Ÿ“Œ First steps.

๐Ÿ“Œ Creating the component to display the map.

๐Ÿ“Œ Showing the map on screen.

๐Ÿ“ Keeping the reference to the map container.

๐Ÿ”ด Why do we need to keep the reference?

๐Ÿ“ Initializing MapBox.

๐Ÿ“Œ Adding a marker at the initial position.

๐Ÿ“ Before our component grows.

๐Ÿ“ Listening for the 'load' event in the map.

๐Ÿ”ด Creating the function to add markers.

๐Ÿ“ Showing the marker.

๐Ÿ“Œ Adding a new marker on the map when double clicked.

๐Ÿ“Œ Conclusion.

๐Ÿ“Œ Source code.

ย 

๐Ÿงต Technologies to be used.

  • โ–ถ๏ธ React JS (v.18)
  • โ–ถ๏ธ Vite JS
  • โ–ถ๏ธ TypeScript
  • โ–ถ๏ธ MapBox
  • โ–ถ๏ธ 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then we install the dependencies.

npm install
Enter fullscreen mode Exit fullscreen mode

Then we open the project in a code editor (in my case VS code).

code .
Enter fullscreen mode Exit fullscreen mode

ย 

๐Ÿงต First steps.

We need to install MapBox in our application:

npm i mapbox-gl
Enter fullscreen mode Exit fullscreen mode

And since we are using TypeScript, we need to install the MapBox types:

npm i -D @types/mapbox-gl
Enter fullscreen mode Exit fullscreen mode

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.

const App = () => {
  return (
    <div>
        <h1>Hello World</h1>
    </div>
  )
}
export default App
Enter fullscreen mode Exit fullscreen mode

๐Ÿšจ 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'
Enter fullscreen mode Exit fullscreen mode

This is what the src/main.tsx file would look like

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>
)
Enter fullscreen mode Exit fullscreen mode

ย 

๐Ÿงต Creating the component to display the map.

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.

export const MapView = () => {
    return (
        <div className='map' />
    )
}
Enter fullscreen mode Exit fullscreen mode

ย 

๐Ÿงต Showing the map on screen.

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.

ย 

๐ŸŸ  Keeping the reference to the map container.

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

import { useRef } from 'react';

export const MapView = () => {

    const mapRef = useRef<HTMLDivElement>(null);

    return <div ref={mapRef} className='map' />
}
Enter fullscreen mode Exit fullscreen mode

ย 

๐Ÿ”ด Why do we need to keep the reference?

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.

import { Map } from 'mapbox-gl';

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

}
Enter fullscreen mode Exit fullscreen mode

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.

import { Map } from 'mapbox-gl';

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

    return new Map();
}
Enter fullscreen mode Exit fullscreen mode

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.

  • 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. ๐Ÿ˜Œ

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
    });

}
Enter fullscreen mode Exit fullscreen mode

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] ).

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' />
    )
}
Enter fullscreen mode Exit fullscreen mode

Now, we would see the map on screen ๐Ÿฅณ, as in this image:

map

Well, what now?
How about we add some events to add bookmarks ๐Ÿ˜‰.

ย 

๐Ÿงต Adding a marker at the initial position.

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.

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

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

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

    }
}, []);
Enter fullscreen mode Exit fullscreen mode

ย 

๐ŸŸ  Before our component grows...

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.

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]
            );

        }
    }, []);
}
Enter fullscreen mode Exit fullscreen mode

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

And thus we will have our component, much more readable. ๐Ÿ˜‰

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' />
}
Enter fullscreen mode Exit fullscreen mode

ย 

๐ŸŸ  Listening for the 'load' event in the map.

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(() => {

}, [])
Enter fullscreen mode Exit fullscreen mode

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()'.

useEffect(() => {

    mapInitRef.current && mapInitRef.current.on();

}, [])
Enter fullscreen mode Exit fullscreen mode

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.
useEffect(() => {

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

}, [])
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ด Creating the function to add markers.

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.
import { Map } from 'mapbox-gl';

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

}
Enter fullscreen mode Exit fullscreen mode

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.
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 })
}
Enter fullscreen mode Exit fullscreen mode

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.

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])
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

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

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)
}
Enter fullscreen mode Exit fullscreen mode

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

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)
}
Enter fullscreen mode Exit fullscreen mode

It's time to call this method! ๐Ÿ˜‰

ย 

๐ŸŸ  Showing 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.

useEffect(() => {
    mapInitRef.current && mapInitRef.current.on(
        'load', 
        () => generateNewMarker()
}, [])
Enter fullscreen mode Exit fullscreen mode

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.
useEffect(() => {
    mapInitRef.current && mapInitRef.current.on(
        'load', 
        () => generateNewMarker({ 
            map: mapInitRef.current!, 
            ...mapInitRef.current!.getCenter() 
        })
}, [])
Enter fullscreen mode Exit fullscreen mode

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.

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

    return () => { 
        mapInitRef.current?.off('load', generateNewMarker) 
    }
}, [])
Enter fullscreen mode Exit fullscreen mode

ย 

This is what the marker would look like on our map. ๐Ÿฅณ

initial marker

ย 

๐Ÿงต 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.

useEffect(() => {

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

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

}, [])
Enter fullscreen mode Exit fullscreen mode

ย 

This is how the markers would look on our map. ๐Ÿฅณ

markers

ย 

๐Ÿงต Conclusion.

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! โค๏ธ

ย 

๐Ÿงต Source code.

GitHub logo Franklin361 / show-map

Application to display a map from the MapBox library and execute events to add markers on the map. ๐Ÿ—บ๏ธ

Show MapBox map with React. ๐Ÿ—บ๏ธ

Application to display a map from the MapBox library and execute events to add markers on the map. ๐Ÿ—บ๏ธ

ย 

Image or Gif

ย 

Features โš™๏ธ

  1. View a full screen map.
  2. Place a marker at the initial position when loading the map.
  3. Add a new marker when double clicking on the map.

ย 

Technologies ๐Ÿงช

  • React JS
  • TypeScript
  • Vite JS
  • MapBox

ย 

Installation ๐Ÿงฐ

  1. Clone the repository (you need to have Git installed).
    git clone https://github.com/Franklin361/show-map
Enter fullscreen mode Exit fullscreen mode
  1. Install dependencies of the project.
    npm install
Enter fullscreen mode Exit fullscreen mode
  1. Run the project.
    npm run dev
Enter fullscreen mode Exit fullscreen mode

Note: For running the tests, use the following command

    npm run test
Enter fullscreen mode Exit fullscreen mode

ย 

Links โ›“๏ธ

Demo of the application ๐Ÿ”ฅ

Here's the link to the tutorial in case you'd like to take a look at it! eyes ๐Ÿ‘€

  • ๐Ÿ‡ฒ๐Ÿ‡ฝ ๐Ÿ”—

  • ๐Ÿ‡บ๐Ÿ‡ฒ ๐Ÿ”—






Top comments (3)

Collapse
 
egolegegit profile image
egolegegit

well presented! Thanks!

Collapse
 
flash010603 profile image
Usuario163

Nice post! keep it up

Collapse
 
ask9691 profile image
Ashok Patel

Thanks .
Works for me.