DEV Community

loading...

Using mapbox-gl in React

justincy profile image Justin ・2 min read

Mapbox has a great tutorial 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.
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.

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.

/**
 * 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:

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' }} />;
}

Discussion

pic
Editor guide
Collapse
tomvalorsa profile image
Tom Valorsa

This is cool, thanks for sharing. Have you used any of the popular React wrappers for mapbox gl, like react-map-gl or react-mapbox-gl? Would be interested to hear how this compares in your experience, if you feel like you have more control etc.

Collapse
justincy profile image
Justin Author

I looked at some of those. They have a different philosophy by trying to put everything into React. I can see the appeal there, and maybe it'd be beneficial for really dynamic and interactive maps. I didn't felt the need to add a thick and complex layer between React and mapbox. I just needed to render a map in the right place at the right time. I was able to accomplish that with ~20 lines of code and no extra dependencies. Perhaps my usecase is just really basic.