How to Build a Mapbox Minimap in a React App

Tyler Beall
9 min readOct 14, 2021

--

Our goal is to build a web application containing a responsive map and overview map using Mapbox GL JS and React.

Backstory

My team at Evans and Chambers Technology has been working on an application in the geospatial analytics field for the past few months. Using the React library we were able to provide a map which would allow users to:

  • Draw and isolate GeoJSON shapes
  • Identify locations on a map
  • Provide layers for data analytics and trends
  • Have an overview map of the user’s current view

After testing most of the major libraries, we landed on Mapbox GL JS. Not only is it an extremely robust and powerful tool, the pricing is competitive as well. Additionally, it would be worthwhile to mention that it integrates well with React. It also has great documentation with a strong user base.

However, one major feature missing from Mapbox is a built-in overview map. We could not find any examples on their website, or anywhere online, on how to implement this relatively common feature request.

That is when I stumbled on the Google Maps API — which for many of the reasons above did not fit into our toolchain. However, when exploring how they render their inset map, I discovered a remarkably transparent fact:

They just render 2 maps.

Caveats

That’s right! In order to show an overview map in Mapbox you need to manually render 2 maps.

So right out of the gate, I wanted to be transparent about the pros and cons of this feature. There will be two map calls performed on every new page render, resulting in double account usage. For each account, Mapbox gives 50,000 renders for free every month. All additional renders, thereafter, are charged per thousand. This is something to keep in mind as you design your application.

The final caveat before we start building: this tutorial requires that you have node/npm installed and that you are familiar with React, JavaScript, and some basic HTML and CSS. Also please keep in mind that this instruction only focuses on the integration of Mapbox with React, and hooking up an overlay map.

With those things aside, let’s get to building!

Module 1… Create Base React Project

The first major step is to set up your base React project. If you already have an application up and running, simply create a blank Mapbox component and skip to module 2, otherwise continue below.

Follow the directions on React’s website to create our base app.

Open a terminal and navigate to the directory that you would like to build this project in. Then run the commands:

npx create-react-app mapbox-minimap 
cd mapbox-minimap
npm start

You should see the traditional base React application page.

The initial setup is looking good. Let’s go ahead and clear out some unnecessary HTML and CSS. Delete everything in your App.css file and clean up your App.js so that it looks like:

Next, create a new React functional component in your src folder, let’s call it MapboxMap.js

Let’s then go and import this new component to your main App.js file and add it to your render call.

Save all of your files and navigate back to your browser.

Great, our base React app is ready to incorporate with Mapbox.

Close out your locally hosted server in the terminal with control + c. We have a bit of work to do before we fire it back up.

Module 2… Install and Setup Base Mapbox Code

Follow the instructions on Mapbox’s website to setup an account and create a web token. We will need this to render a map later on.

Then we will follow a modified version of the page below to set up our application with a base Mapbox map. Please note the library version differences between my example and the code on their website. If there are any conflicts between React and Mapbox see their troubleshooting page.

The first step in the Mapbox setup is installing the mapbox-gl library into our application.

Open up your terminal again and run npm install mapbox-gl.

Next navigate to your src/index.js file and add in the css import to include Mapbox’s styling.

Navigate back to your MapboxMap.js component, where we are going to add in the token, and the Mapbox library import so we can start working with it.

In the same file, we are going to add the base map element to your HTML as well as some basic state management with the useState hook, for the the zoom, latitude, and longitude while you use the map.

A quick explanation on these changes…

We have added a div element to our return function on our component which is directly linked (by reference/useRef hook) to our Mapbox rendering code. This will later allow us to work with the map programmatically.

However, that connection alone would not load the map, since we have to connect our map instance with a token as well as the rendering properties provided from the mapbox-gl library. We will do so within a useEffect on the component, which would mean that our map renders only once when the component first loads.

Finally, we need to add a height to the map in order for the div with the Mapbox reference to render. You can add the height style either inline on your mapContainer div or via class designation. To keep things simple, we are going to add most of the styling right to our src/index.css file.

Let’s fire up this application and see if we can render a map. Once again run npm start from your terminal and navigate to localhost:3000 in your browser. You should be seeing something like the image below. Additionally, the map should be interactive in that you can move around.

Module 3… Adding an Overview Map

The first step in adding our overview map is going to be reorganizing our code so that our map building functionality is no longer stored directly in our useEffect call. The code between building both maps can become extensive, particularly if you are adding additional features to the maps, outside of this tutorial (such as drawing, manipulating GeoJSON, etc).

Additionally, we will also now add a max and min zoom to our map. This will help with the user experience between the two maps — which we will explore further below.

To add our overview map, we will need to follow a similar pattern that was used for our original map. We will add the generation of our overview map inside of our buildMaps function, as well as adding the HTML reference into the return call in the component. Be mindful of the different properties on the overview map, such as being read-only.

For the CSS portion, we have a few major updates which we will add to our src/index.css file.

If our goal is to position an overview map on the top left corner of our parent map, the most consistent way to do this across browsers is to make our parent div within this component relatively positioned and then absolutely position our overview map on top of that.

We will also need to update the map-container class on both maps to have a width and height of 100% — that is, they will now take up the size of their parent container. This is our first step in making both maps resizable.

When you save and reload the application, you will notice that the map no longer renders, why is this?

As it turns out, Mapbox maps — at some point in their DOM tree — need to be defined with a fixed height. This is a little antithetical to normal DOM rendering. Traditionally the children elements will define how much space that they need, and the parent elements will work with those sizes (grow, shrink, wrap, overflow, etc). But with Mapbox, you have to define the parent in order for the child to render a size.

And in our case, when we replaced the fixed height from the parent map with a percentage-based measurement, the size requirement now aligns to the mapbox-parent-container div, which also does not have a static size. Therefore, your map will no longer have a fixed height, and thus will no longer render.

There are several possible solutions to this problem. The first being that you could continue using a fixed size within the mapbox-parent-container div.

And indeed, if you revert your CSS, now your map will render. However, you have effectively removed the ability for your map to be responsive to size changes.

A second solution is to offload the height and width requirements to components higher in our DOM tree. In our case, we will have our App.js fulfill those static size requirements.

Note you could wrap your MapboxMap component inside of any bootstrap, flex, or grid type of structure, as long as the height is defined in any form other than a percentage.

These updates result in resizable main and overview maps.

Module 4… Styling Overview Map

Our last feature is to provide a more true “overview” zoom functionality and to create styled bounds within our overview map to indicate the region that we are viewing on our main map, as defined in the steps below.

Update buildMaps() Function and Map Center Tracking

We need to update the buildMaps function so that any time that there is a movement on the parent map, we want the overview map to track and match the location of the parent map.

The overview map is now tracking with the parent movement. But something seems off with the zoom, particularly when we zoom too far out or too far in. See below.

Modify Zoom on Overview Map

We need to implement a modified zoom to the overview map so that the tracking is a little more consistent across zoom levels.

For this, we will create a function in MapboxMap.js that takes in a zoom level (i.e. the current parent zoom), and finds the smallest zoom, within a min/max limit, in order to have the overview map be appropriately zoomed out in comparison to the main map.

To note: smaller numbers mean zoomed out; larger numbers mean zoomed in.

Note that we also added some constants for this function near the top of the component definition. These numbers were gathered simply through trial and error. So feel free to update these to your desired style.

With our custom buildOverviewZoom function, we will apply this modified zoom functionality to the zoom property of the overview map when the overview map is rendering and when the main map is moving.

This now provides a richer experience for showing a more precise overview of the geographic area that you are viewing.

Creating Stylistic Bounds on Overview Map

The last step with our overview map is to add styled bounds which indicate the outside border of the main map view boundaries.

For this, we will create a function called buildOverviewBounds that will first remove old bounds. Then it will get the current parent map bounds, and add geoJSON features to the overview map. Finally, it will draw colored layers for the border and fill that geoJSON feature.

Finally, we call this function any time that the overview map is rendering or changing, within the buildMaps() function.

And this results in responsive colored bounds on your overview map which are dynamic to the parent map.

And for closure, here is the full final MapboxMap.js component code.

Hopefully, this has been helpful in providing a workflow for setting up a Mapbox map in a React application, as well as giving some enhanced functionality outside of Mapbox’s documentation. Feel free to leave your thoughts or concerns within the comments.

--

--

Tyler Beall

I am a software engineer with experience in designing and building robust and beautiful business applications.