DEV Community

Cover image for Setting up React Leaflet with Mapbox and Vite
Francisco Mendes
Francisco Mendes

Posted on

Setting up React Leaflet with Mapbox and Vite

I believe that everyone at some point has added a map to a website, or tried to create an app to share with friends all the places they've traveled.

And also at some point, you used iframes to add maps on websites, at least to show where a business or a store is located.

However we are stuck with what is offered to us by the services we use, such as Google's apis, but nowadays we have an amazing service called Mapbox where we can style the maps the way we want and have a free plan that in my opinion seems to be generous.

First we will make a map using only the tiles that come by default in Leaflet. And if you are satisfied with the result, you can leave it at that. But if you want to use the Mapbox tiles, read the article until the end.

The application we are going to make today will only serve to show your current location. For this we will use the Geolocation Web API, this if the user allows access to the location, otherwise we will make an http request to ipapi (it's not that precise but it helps).

And this time, instead of using the webpack as a bundler, I'm going to use Vite, if you never used it now is the chance to do so.

Let's code

First let's create our project with Vite, for that we'll use the following command:

npm init @vitejs/app [PROJECT_NAME]
Enter fullscreen mode Exit fullscreen mode

Now we can interact with the terminal, first we select our framework, which in our case is react and then the JavaScript language.

terminal

Then we will go to our project folder to install the dependencies and start the development environment:

cd [PROJECT_NAME]
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

You should now have an app like this on port 3000:

basic app

Now we can install the necessary dependencies to be able to work with Leaflet in React:

npm install react-leaflet leaflet axios
Enter fullscreen mode Exit fullscreen mode

First we have to import the leaflet styles into the main file of our application:

// @src/main.jsx

import React from "react";
import ReactDOM from "react-dom";

import "./index.css";
import App from "./App";
import "leaflet/dist/leaflet.css"; // <- Leaflet styles

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);
Enter fullscreen mode Exit fullscreen mode

Now we are going to import the React Leaflet components needed to get the map. Make sure to set a height and width for the map.

// @src/app.jsx

import React from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";

const App = () => {
  const position = [51.505, -0.09];
  return (
    <MapContainer
      center={position}
      zoom={13}
      scrollWheelZoom={true}
      style={{ minHeight: "100vh", minWidth: "100vw" }}
    >
      <TileLayer
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={position}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

You should now have an application similar to this one:

default app

As you may have noticed in the code, we have a static position, but since we need to have a dynamic position, we will create a hook to get its current position.

Let's call our hook useMap:

// @src/hooks/index.jsx

export const useMap = () => {
  // Logic goes here
};
Enter fullscreen mode Exit fullscreen mode

First we will create our state using the useState hook, and in it we will store our latitude and longitude positions. I will want the initial state to be in Nantes, France but you can choose another location.

// @src/hooks/index.jsx

import { useState } from "react";

export const useMap = () => {
  const [position, setPosition] = useState({
    lat: 47.21725,
    lng: -1.55336,
  });
  // More logic goes here
};
Enter fullscreen mode Exit fullscreen mode

Then we will use the useEffect hook to make it run only when the page is rendered for the first time. And we know that the function's return is just going to be the position.

// @src/hooks/index.jsx

import { useState, useEffect } from "react";

export const useMap = () => {
  const [position, setPosition] = useState({
    lat: 47.21725,
    lng: -1.55336,
  });
  useEffect(() => {
    // More logic goes here
  }, []);
  return { position };
};
Enter fullscreen mode Exit fullscreen mode

The next step is to access our location via the Web API and we will store those same data.

// @src/hooks/index.jsx

import { useState, useEffect } from "react";
import axios from "axios";

export const useMap = () => {
  const [position, setPosition] = useState({
    lat: 47.21725,
    lng: -1.55336,
  });
  useEffect(() => {
    navigator.geolocation.getCurrentPosition(
      ({ coords }) => {
        setPosition({ lat: coords.latitude, lng: coords.longitude });
      },
      (blocked) => {
        // More logic goes here
        }
      }
    );
  }, []);
  return { position };
};
Enter fullscreen mode Exit fullscreen mode

However, if the user (or the device he is using) blocks access to his location, we will have to make an http request to an Api. For this we will use the axios and we will store the response data in our state.

The final code of our hook should look like this:

// @src/hooks/index.jsx

import { useState, useEffect } from "react";
import axios from "axios";

export const useMap = () => {
  const [position, setPosition] = useState({
    lat: 47.21725,
    lng: -1.55336,
  });
  useEffect(() => {
    navigator.geolocation.getCurrentPosition(
      ({ coords }) => {
        setPosition({ lat: coords.latitude, lng: coords.longitude });
      },
      (blocked) => {
        if (blocked) {
          const fetch = async () => {
            try {
              const { data } = await axios.get("https://ipapi.co/json");
              setPosition({ lat: data.latitude, lng: data.longitude });
            } catch (err) {
              console.error(err);
            }
          };
          fetch();
        }
      }
    );
  }, []);
  return { position };
};
Enter fullscreen mode Exit fullscreen mode

Now we can go back to our map component again and we can import our hook to access our location in a dynamic way. And we'll change the zoom of the map from 13 to 4.5 (to see a larger area).

// @src/app.jsx

import React from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";

import { useMap } from "./hooks";

const App = () => {
  const { position } = useMap();
  return (
    <MapContainer
      center={position}
      zoom={4.5}
      scrollWheelZoom={true}
      style={{ minHeight: "100vh", minWidth: "100vw" }}
    >
      <TileLayer
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={position}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

The current result should look very similar to this:

default map

If you are satisfied with the result, you can stop here, but if you want to have some different tiles, keep reading the article because now we are going to use the Mapbox tiles.

First go to the Mapbox website and create an account.

mapbox website

Then go to Mapbox Studio and create a new style.

mapbox dashboard

mapbox studio

Then you can select the template that you want and its variant. In this case I will use the Basic template and the Galaxy variant.

select template

In the configuration UI of the map, click on share and check if in the production tab you can find the Style URL and the Access Token.

share

Now at the root of our project, let's create an .env to store our environment variables. In the Style URL link you will have the username and the style id.

VITE_USERNAME=
VITE_STYLE_ID=
VITE_ACCESS_TOKEN=
Enter fullscreen mode Exit fullscreen mode

Now back to our map component, let's import our environment variables as follows:

// @src/app.jsx

import React from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";

import { useMap } from "./hooks";

const { VITE_USERNAME, VITE_STYLE_ID, VITE_ACCESS_TOKEN } = import.meta.env;

// Hidden for simplicity
Enter fullscreen mode Exit fullscreen mode

And in the <TileLayer /> component, we're going to replace the attribution and the url. In the url we will add the link to get Mapbox tiles by dynamically passing the values of our environment variables. Just like we're going to give Mapbox credits in attribution. Like this:

// @src/app.jsx

// Hidden for simplicity

const App = () => {
  const { position } = useMap();
  return (
    <MapContainer
      center={position}
      zoom={4.5}
      scrollWheelZoom={true}
      style={{ minHeight: "100vh", minWidth: "100vw" }}
    >
      <TileLayer
        attribution='Imagery &copy; <a href="https://www.mapbox.com/">Mapbox</a>'
        url={`https://api.mapbox.com/styles/v1/${VITE_USERNAME}/${VITE_STYLE_ID}/tiles/256/{z}/{x}/{y}@2x?access_token=${VITE_ACCESS_TOKEN}`}
      />
      // Hidden for simplicity
    </MapContainer>
  );
};
Enter fullscreen mode Exit fullscreen mode

The map component code should be as follows:

// @src/app.jsx

import React from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";

import { useMap } from "./hooks";

const { VITE_USERNAME, VITE_STYLE_ID, VITE_ACCESS_TOKEN } = import.meta.env;

const App = () => {
  const { position } = useMap();
  return (
    <MapContainer
      center={position}
      zoom={4.5}
      scrollWheelZoom={true}
      style={{ minHeight: "100vh", minWidth: "100vw" }}
    >
      <TileLayer
        attribution='Imagery &copy; <a href="https://www.mapbox.com/">Mapbox</a>'
        url={`https://api.mapbox.com/styles/v1/${VITE_USERNAME}/${VITE_STYLE_ID}/tiles/256/{z}/{x}/{y}@2x?access_token=${VITE_ACCESS_TOKEN}`}
      />
      <Marker position={position}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

The end result of our application should look as follows:

final

I hope it helped and that it was easy to understand! 😁
Have a nice day! 😉

Discussion (0)