31
loading...
This website collects cookies to deliver better user experience
mapbox-gl
in aReact
application, using the example of creating a simple web application containing a map on Next.js
using Typescript
, the map component code can also be used in any React
applicationReact
componentReact
For comfortable reading of this article, you need to have
basic knowledge of React
,Typescript
and CSS
All code snippets will be using Typescript
, using typing
in javascript is the best practice, so I basically stick to
it where possible, I apologize if you are not familiar with
it, here is a great course from egghead.io where you can read it
I prefer to import React
as import * as React from
you can read more about this in
"react"
great article by Kent C. Dodds
If // ...
is encountered in the code, it must be read as
places with missing duplicate code
Next.js
using the Typescript
template.npx create-next-app --typescript my-awesome-app
mapbox-gl
with types for Typescript
cd my-awesome-app
npm install --save mapbox-gl && npm install -D @type/mapbox-gl
mapbox-gl
, from environment variable so as not to store it directly in the source codetouch .env.local
echo NEXT_PUBLIC_MAPBOX_TOKEN=<your_token> >> .env.local
Next.js
NEXT_PUBLIC_MAPBOX_TOKEN=<your_token>
rm styles/Home.module.css
html,
body,
#__next {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
* {
box-sizing: border-box;
}
100%
of the window height, set the properties width
and height
to 100%
for html
and body
css
selector#__ next
because in the Next.js
application the root element is<div id = "__ next"> ... </div>
import * as React from "react";
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
// import the mapbox-gl styles so that the map is displayed correctly
function MapboxMap() {
// this is where the map instance will be stored after initialization
const [map, setMap] = React.useState<mapboxgl.Map>();
// React ref to store a reference to the DOM node that will be used
// as a required parameter `container` when initializing the mapbox-gl
// will contain `null` by default
const mapNode = React.useRef(null);
React.useEffect(() => {
const node = mapNode.current;
// if the window object is not found, that means
// the component is rendered on the server
// or the dom node is not initialized, then return early
if (typeof window === "undefined" || node === null) return;
// otherwise, create a map instance
const mapboxMap = new mapboxgl.Map({
container: node,
accessToken: process.env.NEXT_PUBLIC_MAPBOX_TOKEN,
style: "mapbox://styles/mapbox/streets-v11",
center: [-74.5, 40],
zoom: 9,
});
// save the map object to React.useState
setMap(mapboxMap);
return () => {
mapboxMap.remove();
};
}, []);
return <div ref={mapNode} style={{ width: "100%", height: "100%" }} />;
}
export default MapboxMap
mapbox-gl
init parameters can be found in the documentationimport MapboxMap from "../components/mapbox-map";
function App() {
return <MapboxMap />;
}
export default App;
npm run dev
props
props
for the MapboxMap
componentcontainer
property of the MapboxOptions
interface is not required in this case, to exclude it we use the utility type Omit
initialOptions
to the web map init options using spread syntax, we will also set a callback for the map load
event// ...
const mapboxMap = new mapboxgl.Map({
container: node,
accessToken: process.env.NEXT_PUBLIC_MAPBOX_TOKEN,
style: "mapbox://styles/mapbox/streets-v11",
center: [-74.5, 40],
zoom: 9,
...initialOptions,
});
setMap(mapboxMap);
// if onMapLoaded is specified it will be called once
// by "load" map event
if (onMapLoaded) mapboxMap.once("load", onMapLoaded);
// removing map object and calling onMapRemoved callback
// when component will unmout
return () => {
mapboxMap.remove();
if (onMapRemoved) onMapRemoved();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// ...
// eslint-disable-next-line react-hooks/exhaustive-deps
react-hooks/exhaustive-deps
rule we had to specify in the list of dependencies for React.useEffect
variables added to the hook [initialOptions, onMapLoaded]
initialOptions
or onMapLoaded
was changed, you can read more about using React.useEffect
at the link belowimport * as React from "react";
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
interface MapboxMapProps {
initialOptions?: Omit<mapboxgl.MapboxOptions, "container">;
onMapLoaded?(map: mapboxgl.Map): void;
onMapRemoved?(): void;
}
function MapboxMap({ initialOptions = {}, onMapLoaded }: MapboxMapProps) {
const [map, setMap] = React.useState<mapboxgl.Map>();
const mapNode = React.useRef(null);
React.useEffect(() => {
const node = mapNode.current;
if (typeof window === "undefined" || node === null) return;
const mapboxMap = new mapboxgl.Map({
container: node,
accessToken: process.env.NEXT_PUBLIC_MAPBOX_TOKEN,
style: "mapbox://styles/mapbox/streets-v11",
center: [-74.5, 40],
zoom: 9,
...initialOptions,
});
setMap(mapboxMap);
if (onMapLoaded) mapboxMap.once("load", onMapLoaded);
return () => {
mapboxMap.remove();
if (onMapRemoved) onMapRemoved();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div ref={mapNode} style={{ width: "100%", height: "100%" }} />;
}
export default MapboxMap;
onMapLoaded
callback when it is loaded. We can also use onMapLoaded
to store a link to the map instance in the parent component, for example. We can also use onMapRemoved
if we need to know that the map instance has been removed.MapLoadingHolder
component that will be displayed on top of the map until it is loaded.svg
icon for the loading screen. I have it from https://www.freepik.com, and then converted it to jsx
format using https://svg2jsx.com/function WorldIcon({ className = "" }: { className?: string }) {
return (
<svg
className={className}
xmlns="http://www.w3.org/2000/svg"
width="48.625"
height="48.625"
x="0"
y="0"
enableBackground="new 0 0 48.625 48.625"
version="1.1"
viewBox="0 0 48.625 48.625"
xmlSpace="preserve"
>
<path d="M35.432 10.815L35.479 11.176 34.938 11.288 34.866 12.057 35.514 12.057 36.376 11.974 36.821 11.445 36.348 11.261 36.089 10.963 35.7 10.333 35.514 9.442 34.783 9.591 34.578 9.905 34.578 10.259 34.93 10.5z"></path>
<path d="M34.809 11.111L34.848 10.629 34.419 10.444 33.819 10.583 33.374 11.297 33.374 11.76 33.893 11.76z"></path>
<path d="M22.459 13.158l-.132.34h-.639v.33h.152l.022.162.392-.033.245-.152.064-.307.317-.027.125-.258-.291-.06-.255.005z"></path>
<path d="M20.812 13.757L20.787 14.08 21.25 14.041 21.298 13.717 21.02 13.498z"></path>
<path d="M48.619 24.061a24.552 24.552 0 00-.11-2.112 24.165 24.165 0 00-1.609-6.62c-.062-.155-.119-.312-.185-.465a24.341 24.341 0 00-4.939-7.441 24.19 24.19 0 00-1.11-1.086A24.22 24.22 0 0024.312 0c-6.345 0-12.126 2.445-16.46 6.44a24.6 24.6 0 00-2.78 3.035A24.18 24.18 0 000 24.312c0 13.407 10.907 24.313 24.313 24.313 9.43 0 17.617-5.4 21.647-13.268a24.081 24.081 0 002.285-6.795c.245-1.381.379-2.801.379-4.25.001-.084-.004-.167-.005-.251zm-4.576-9.717l.141-.158c.185.359.358.724.523 1.094l-.23-.009-.434.06v-.987zm-3.513-4.242l.004-1.086c.382.405.75.822 1.102 1.254l-.438.652-1.531-.014-.096-.319.959-.487zM11.202 7.403v-.041h.487l.042-.167h.797v.348l-.229.306h-1.098l.001-.446zm.778 1.085s.487-.083.529-.083 0 .486 0 .486l-1.098.069-.209-.25.778-.222zm33.612 9.651h-1.779l-1.084-.807-1.141.111v.696h-.361l-.39-.278-1.976-.501v-1.28l-2.504.195-.776.417h-.994l-.487-.049-1.207.67v1.261l-2.467 1.78.205.76h.5l-.131.724-.352.129-.019 1.892 2.132 2.428h.928l.056-.148h1.668l.481-.445h.946l.519.52 1.41.146-.187 1.875 1.565 2.763-.824 1.575.056.742.649.647v1.784l.852 1.146v1.482h.736c-4.096 5.029-10.33 8.25-17.305 8.25C12.009 46.625 2 36.615 2 24.312c0-3.097.636-6.049 1.781-8.732v-.696l.798-.969c.277-.523.574-1.033.891-1.53l.036.405-.926 1.125a22.14 22.14 0 00-.798 1.665v1.27l.927.446v1.765l.889 1.517.723.111.093-.52-.853-1.316-.167-1.279h.5l.211 1.316 1.233 1.799-.318.581.784 1.199 1.947.482v-.315l.779.111-.074.556.612.112.945.258 1.335 1.521 1.705.129.167 1.391-1.167.816-.055 1.242-.167.76 1.688 2.113.129.724s.612.166.687.166c.074 0 1.372.983 1.372.983v3.819l.463.13-.315 1.762.779 1.039-.144 1.746 1.029 1.809 1.321 1.154 1.328.024.13-.427-.976-.822.056-.408.175-.5.037-.51-.66-.02-.333-.418.548-.527.074-.398-.612-.175.036-.37.872-.132 1.326-.637.445-.816 1.391-1.78-.316-1.392.427-.741 1.279.039.861-.682.278-2.686.955-1.213.167-.779-.871-.279-.575-.943-1.965-.02-1.558-.594-.074-1.111-.52-.909-1.409-.021-.814-1.278-.723-.353-.037.39-1.316.078-.482-.671-1.373-.279-1.131 1.307-1.78-.302-.129-2.006-1.299-.222.521-.984-.149-.565-1.707 1.141-1.074-.131-.383-.839.234-.865.592-1.091 1.363-.69 2.632-.001-.007.803.946.44-.075-1.372.682-.686 1.376-.904.094-.636 1.372-1.428 1.459-.808-.129-.106.988-.93.362.096.166.208.375-.416.092-.041-.411-.058-.417-.139v-.4l.221-.181h.487l.223.098.193.39.236-.036v-.034l.068.023.684-.105.097-.334.39.098v.362l-.362.249h.001l.053.397 1.239.382.003.015.285-.024.019-.537-.982-.447-.056-.258.815-.278.036-.78-.852-.519-.056-1.315-1.168.574h-.426l.112-1.001-1.59-.375-.658.497v1.516l-1.183.375-.474.988-.514.083v-1.264l-1.112-.154-.556-.362-.224-.819 1.989-1.164.973-.296.098.654.542-.028.042-.329.567-.081.01-.115-.244-.101-.056-.348.697-.059.421-.438.023-.032.005.002.128-.132 1.465-.185.648.55-1.699.905 2.162.51.28-.723h.945l.334-.63-.668-.167v-.797l-2.095-.928-1.446.167-.816.427.056 1.038-.853-.13-.131-.574.817-.742-1.483-.074-.426.129-.185.5.556.094-.111.556-.945.056-.148.37-1.371.038s-.038-.778-.093-.778l1.075-.019.817-.798-.446-.223-.593.576-.984-.056-.593-.816h-1.261l-1.316.983h1.206l.11.353-.313.291 1.335.037.204.482-1.503-.056-.073-.371-.945-.204-.501-.278-1.125.009A22.188 22.188 0 0124.312 2c5.642 0 10.797 2.109 14.73 5.574l-.265.474-1.029.403-.434.471.1.549.531.074.32.8.916-.369.151 1.07h-.276l-.752-.111-.834.14-.807 1.14-1.154.181-.167.988.487.115-.141.635-1.146-.23-1.051.23-.223.585.182 1.228.617.289 1.035-.006.699-.063.213-.556 1.092-1.419.719.147.708-.64.132.5 1.742 1.175-.213.286-.785-.042.302.428.483.106.566-.236-.012-.682.251-.126-.202-.214-1.162-.648-.306-.861h.966l.309.306.832.717.035.867.862.918.321-1.258.597-.326.112 1.029.583.64 1.163-.02c.225.579.427 1.168.604 1.769l-.121.112zm-32.331-7.093l.584-.278.528.126-.182.709-.57.181-.36-.738zm3.099 1.669v.459h-1.334l-.5-.139.125-.32.641-.265h.876v.265h.192zm.614.64v.445l-.334.215-.416.077v-.737h.75zm-.376-.181v-.529l.459.418-.459.111zm.209 1.07v.433l-.319.32h-.709l.111-.486.335-.029.069-.167.513-.071zm-1.766-.889h.737l-.945 1.321-.39-.209.084-.556.514-.556zm3.018.737v.432h-.709l-.194-.28v-.402h.056l.847.25zm-.655-.594l.202-.212.341.212-.273.225-.27-.225zm28.55 5.767l.07-.082c.029.126.06.252.088.38l-.158-.298z"></path>
<path d="M3.782 14.884v.696c.243-.568.511-1.122.798-1.665l-.798.969z"></path>
</svg>
);
}
export default WorldIcon;
import WorldIcon from "../components/world-icon";
function MapLoadingHolder() {
return (
<div className="loading-holder">
<WorldIcon className="icon" />
<h1>Initializing the map</h1>
<div className="icon-attribute">
Icons made by{" "}
<a href="https://www.freepik.com" title="Freepik">
Freepik
</a>{" "}
from{" "}
<a href="https://www.flaticon.com/" title="Flaticon">
www.flaticon.com
</a>
</div>
</div>
);
}
export default MapLoadingHolder;
.app-container
element, inside which there will be an absolutely positioned map element placed in a map-wrapper
and a MapLoadingHolder
component<Head> ... </Head>
component, you can specify meta tags and title
for the site with it.loading-holder
, also align its content in the center, add a pulsing animation for the icon, since the background is semi-transparent, add a colored shadow text-shadow: 0px 0px 10px rgba (152, 207, 195 , 0.7);
to the element <h1>Initializing the map</h1>
mapbox-gl
instance outside of React
in my next article.