An approach to integrating MapboxGL in React / Redux

Cole Bessee
Unearth
Published in
7 min readDec 20, 2019

As a geospatial consulting company, we’ve encountered just about every permutation of popular front-end frameworks and mapping libraries. From Backbone with EsriJS to modern Angular with Leaflet, we’ve taken on projects with all sorts of front-end tech stacks. These modern front-end stacks have really improved our efficiency as developers and make it easy to on-board new developers to our projects. Recently, we’ve been using React as a common framework across many of our projects. Likewise, Mapbox has been emerging as a developer favorite for performant map based data visualizations in the browser. React and Mapbox easily pair together for simple use cases and visualizations. However, challenges arise as the complexity of the application grows. The biggest challenge is how to logically organize code within the class structure defined by your framework of choice. In the case of React, we have a front-end JS framework that leverages JSX to allow developers to be more descriptive in their code and abstracts away the need for developers to directly modify the DOM. Meanwhile, mapping libraries like Mapbox provide developers with fine tuned control over the map visualization. In a map based application this means a large chunk of your code will end up in a single “Map” component that listens to user input from various other components to manage and style the map object. Unfortunately that can lead to a Map component with thousands of lines of code and convoluted, difficult to manage integration points with the rest of your application.

Recently, we were tasked with modernizing and enhancing an existing Mapbox based web application. The goal was to bring an improved, documented structure to the codebase that would allow developers to more rapidly add features. Naturally, we reached for React as our framework of choice to bring some structure. In this post, we’ll share the strategy we used to integrate Mapbox GL with React/Redux.

This project uses the following architecture:

  • React with class based components
  • Redux, using an action/reducer pattern, for global state management
  • MapBoxGL JS for mapping visualizations

It’s best if you’re familiar with these patterns and concepts individually before we look into the code and structure associated with integrating them in a more seamless way. Specifically, we’ll look at the component and file structure, how to share the map instance using the React Context API, and useful reducers for modifying stored map state.

The Application

Before diving into specific code, let’s take a look at the basic component hierarchy of our application. The application features a single map instance and a side panel of UI controls that manage different map visualization experiences. Each of these experiences is represented as a single tab in a vertical accordion such that only one accordion tab and therefore one set of data visualizations can be enabled at a time. As each accordion expands, additional UI controls are exposed that allow the user to further customize that map experience. A handful of other UI controls exist outside of the accordion that control persistent data points and can be visualized in any of the map experiences.

The Application and Component Hierarchy

Component and File Structure

For this project, a standardized approach was taken to each component. This approach encapsulates both rendering the UI for controlling the map as well as defining any associated data layers. Examples of components that follow this structure in the above graphic are the map experience controls and the controls for persistent data. For each of these, the component folder is comprised of three files: a JS file which defines the react component class, a sass file for styling the component’s UI, and a layer definitions file. The layer definitions file exports methods for adding the necessary sources and layers to the map in order to support the visualization of data that is controlled by that React component UI. Below are the important aspects of the component file structure.

components/
map/
sidepanel/
PopulationPanel/
PopulationLayers.js
PopulationPanel.js
PopulationPanel.scss
redux/
actions/
reducers/

For this post we’ll be focusing on one of the map experience controls, the PopulationPanel component as an example case. The following layer definition file provides functions for adding a handful of population related data sources and layers to the map.

PopulationLayers.js

Sharing the Map Instance

Now we have functions defined that help us add the data controlled by our component to the map. The ultimate goal is to keep the Map component in charge of defining the basic details and let the various UI components take charge of the management of each layer they control. To do so, we need to provide a reference from our map object to each of our map experience components so they can add/remove data and event listeners to the map. Originally, we thought we might use our existing Redux store to share the map instance across the application. However, this turned out to be far too heavy of an object that caused redux to throw warnings and lag behind. Instead, we leveraged React’s context API to share the map instance created in the map component with the various components in the SidePanel.

First, we simply create a context and export it for use. We store this file in a context folder at the root of our project.

import React from 'react';

const MapContext = React.createContext();

export default MapContext;

Then, in the map component, we define a provider for the MapContext and supply it a value of the map instance to share with all of its children. It’s important that any component that needs access to this value is a child of the provider.

Map.js

The SidePanel and all of its children are now children of the MapContext Provider. Any of these components can now import the context object, set their own context type to the context object, and extract the value from the components context property for use. Using a population component as an example, we can now add the population sources and layers to the map inside our component by using the functions we defined in the adjacent layer definition file.

PopulationPanel.js

Mapbox Style and the Redux Store

Now that we have the ability to define and add map layers in each component, our next step is to manipulate those map layers based on the UI state of our map experience components. In the MapBox GL JS API, the map visualization is defined by the MapBox style object. This object contains definitions for each single data layer available to the map: whether it’s visible, how to filter the data, and directions for how to style the layer. The style of the map needs to be both visible to the map component and available to the other UI components for editing. To do so, we add the map style object to the redux store then, provide actions that each component can use to update specific parts of the style object while the Map component subscribes to this style via props and manages updating the map. The following are some actions and associated reducer cases for common MapBox layer, source, and style updates.

Actions

styleActions.js

Reducer

styleReducers.js

In the map component, we want to set the initial state of style in the redux store and watch for any changes to update the map accordingly. To start, we set up a simple map instance and provide it to the MapContext.Provider as a value.

Map.js

Next, we use redux-connect to connect to the store and provide access to actions via props. The only action we need here is setStyle.

Map.js

To initialize our stored map style, we call the setStyle action after the map loads and pass in the current style object of the map, which we can retrieve by using the getStyle MapBox API method.

Map.js

Finally, using the componentDidUpdate lifecycle method the map component can observe any changes to the style in the store and update itself using the setStyle API method. By default this method will intelligently diff the current map style and only apply the necessary updates rather than rebuilding the map from scratch.

Map.js

The map style object is now part of the redux store, the map component is listening to changes, and updating the map instance to match. Now we can use our redux actions within our PopulationPanel component to change the map visualization based on user interaction with the UI. Earlier we created a source and layer in PopulationLayers.js. Now, we can define two more layers (MB_STATEPOPULATION and MB_COUNTYPOPULATION), however we only want one of these three layers to be visible at a time. To achieve this, we can wire up a set of radio buttons and use the setViz action to toggle the visibility of each layer.

First, we set up our internal state in the component constructor to keep track of which radio button is active.

PopulationPanel.js

Then we define a set of helper functions for adding and removing the layers when each radio button is selected.

PopulationPanel.js

Next, we call these functions in a new function that we can bind to our radio buttons onChange event.

PopulationPanel.js

Leaving us with this final PopulationPanel Component.

PopulationPanel.js

Conclusion

Integrating a modern front-end framework like React with mapping libraries such as MapBox GL JS gives developers the ability to create data rich map visualization applications. Often times, a map centric application can be difficult to manage as the large amount of map related logic causes the main map component to grow to unmaintainable size. Fortunately, we can minimize the growth that occurs in the map component by using the redux store to share and modify the map state, and the React context API to share the map instance across components. This allows us to adopt a component structure that allows individual components to be in charge of their own UI as well as the associated map data, visualizations and interactions.

--

--

Cole Bessee
Unearth
Writer for

Software developer specializing in full-stack web development with an emphasis on open source geospatial technology and data. Also, pugs.