Using mapbox-gl in React
Tags: react, mapbox
Published: 2020-07-22 19:01:39

Mapbox has a [great tutorial](https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/) for getting started with mapbox-gl in React. We want to take it even further by creating a reusable hook component. Our maps won't always look the same: different container sizes, different controls, and different data.

Requirements:

* Centralized location for instantiating the map. We don't want to duplicate the map setup and connection between React and mapbox-gl.
* Flexibility in map options.
* Access to the map instance so that we can add data and event handlers.
* Control over the size of the map container.

```js
import React, { useRef, useState, useEffect } from 'react';
import mapboxgl from 'mapbox-gl';

// Be sure to replace this with your own token
mapboxgl.accessToken = 'MY_ACCESS_TOKEN';

export default function Map({
  center,
  zoom = 17,
  width = '100%',
  height = '300px',
  onInit,
}) {
  const ref = useRef(null);
  const [map, setMap] = useState(null);
  useEffect(() => {
    // Don't create the map until the ref is connected to the container div.
    // Also don't create the map if it's already been created.
    if (ref.current && !map) {
      const map = new mapboxgl.Map({
        container: ref.current,
        style: 'mapbox://styles/mapbox/streets-v11',
        center,
        zoom,
      });
      setMap(map);
      onInit(map);
    }
  }, [ref, center, zoom, map, onInit]);
  return <div ref={ref} style={{ width, height }} />;
}
```

That's great, but it still only allows us to change the width and height of the map container. That's probably all you need, but if you want more control then you have two options.

## 1. Add a className prop to the component.

Adding a `className` prop allows you to specify a custom style for the map container. You could also accept a `styles` prop if you prefer inline styles, but I prefer `className`.

```js
import React, { useRef, useState, useEffect } from 'react';
import mapboxgl from 'mapbox-gl';

// Be sure to replace this with your own token
mapboxgl.accessToken = 'MY_ACCESS_TOKEN';

export default function Map({
  center,
  zoom = 17,
  className,
  onInit,
}) {
  const ref = useRef(null);
  const [map, setMap] = useState(null);
  useEffect(() => {
    // Don't create the map until the ref is connected to the container div.
    // Also don't create the map if it's already been created.
    if (ref.current && !map) {
      const map = new mapboxgl.Map({
        container: ref.current,
        style: 'mapbox://styles/mapbox/streets-v11',
        center,
        zoom,
      });
      setMap(map);
      onInit(map);
    }
  }, [ref, center, zoom, map, onInit]);
  return <div ref={ref} classname={className} />;
}
```

## 2. Turn this into a custom hook

Using a custom hook means you've centralized instantiation of the map but have full control over the container it's attached to.

```js
/**
 * useMap.js
 */
import mapboxgl from 'mapbox-gl';
import React, { useRef, useEffect, useState } from 'react';

// Be sure to replace this with your own token
mapboxgl.accessToken = 'MY_ACCESS_TOKEN';

export default function useMapbox({
  center,
  zoom = 17,
  onInit
}) {
  const ref = useRef(null);
  const [map, setMap] = useState(null);
  useEffect(() => {
    if (ref.current && !map) {
      const map = new mapboxgl.Map({
        container: ref.current,
        style: 'mapbox://styles/mapbox/streets-v11',
        center,
        zoom,
      });
      setMap(map);
      onInit(map);
    }
  }, [ref, center, zoom, map]);
  return { ref };
}
```
Then we use the hook in a component:

```js
import useMap from './useMap';

function FancyMap() {
  const onInitHandler = map => {
    // Add data and events here
  }
  const { ref } = useMap({ center, zoom, onInit: onInitHandler });
  return <div ref={ref} style={{ width: '100%', height: '300px' }} />;
}
```