Want to use React/Redux for Mapbox apps, but aren’t sure where to start? Read on!
I work in R&D in the architecture and real-estate development world at Ankrom Moisan Architects. That means two things:
- I use maps. A LOT. Most things we do are geospatially oriented. Mapbox has been invaluable for this. I can control every aspect of how the map looks (presentation is everything, especially for architecture), and it doesn’t complain when I throw crazy amounts of data at it.
- I often need to iterate on several complex applications quickly, and force my brain to change gears frequently (I’m also doing design and construction work).
Building large applications using the imperative and procedural techniques typically shown in the mapbox examples can quickly make application flow difficult to understand, and state difficult to trace. That second point became a force multiplier on top of those challenges, slowing down development, and making errors more likely.
The solution that ended up working perfectly for me was transitioning to building applications with React and Redux; but it wasn’t immediately clear how to work Mapbox into this framework.
Fortunately, the wonderful folks at Mapbox have actually thought quite a bit about this, and Tom MacWright wrote a very helpful Medium post on the subject. I felt it might be helpful for other geo-develeopers to have more concrete examples to looks at. Thus, I have a brief tutorial:
Building a simple React/Redux/Mapbox app:
I recently put together a dataset combining lots of meta-data about buildings in Portland, Oregon and their 3D geometries. I needed to build a simple interface so users could switch between data-properties, like square feet and unit count, in the visualization. You can see the final product here.
The entire application is very simple, and I’ve put it on github. In this post, I’ll go through the steps required to make a flexible React/Redux wrapper around mapbox. Side note: this example is simple enough to not really need react/redux, but the process shown will scale well.
Before we code, there is one important thing to keep in mind when trying to manage application state with Mapbox: the map’s stylesheet is the single source of truth for everything. If you have the current stylesheet, you know exactly what state the map is in.
To follow React’s declarative philosophy of programming then, we’d like to take the stylesheet and modify it to reflect what we want to see, and hand it back to the map to render.
On its own, mapbox will not accept a stylesheet as an update. However, as they outlined in Tom’s post linked above, the Mapbox developer’s wrote a diff’ing algorithm for stylesheets that lives in the style-spec github repo. It takes in two stylesheets, one representing how the map looks now and one representing what you want it to look like, and produces a set of commands that will take your map from one style to the other.
With this knowledge, and the diffStyles function, we can outline a process for wrapping Mapbox:
- Create a ‘map’ object with mapbox-gl-js inside of a React component.
- When the map has loaded, grab its stylesheet object and put it in the application state.
- When user interaction requires that the style changes, have a reducer grab a copy of the style-state and change the properties you wish inside of it.
- This change to the state tree will trigger an update that can be hooked with “componentWillRecieveProps” in the map component. Within this function you can grab the current stylesheet, and the new stylesheet (from nextProps). Then diff the two with diffStyles, and apply the changes.
1 We’ll start with a component that just returns an empty div identified as ‘map’, just like in traditional html:
In a non-react setting, we would have a script that identifies this div and instantiates a map canvas. That’s exactly what we’ll do here too, but we need to make sure that it happens at the appropriate time in the React lifecycle, so we’ll put it in “componentDidMount”:
2 Now we have the initial stylesheet in our variable called ‘style’, and we need to save it into our application state.
First a brief aside on the contents of this variable: If you’ve never really looked at the stylesheet before (I hadn’t), go ahead and explore it in the console, you will see that it is a deeply nested JSON document who’s structure can vary depending on layer order and property choices.
For this reason (as well as innumerable other benefits) I highly reccomend using Immutable.js to wrap the stylesheet before you store it. Immutable has numerous methods for finding specific properties within deeply nested objects and lists, and will save you a lot of time. It will also give you confidence that only things you want changing the style will be able to.
So let’s write an action and reducer to take in a JSON stylesheet and save it to our application state as an Immutable object:
3 Now let’s assume that in some other React component we have buttons a user can click to change the property governing the color of our buildings. The buttons will trigger an action called ‘CHANGE_VIZ’, and the payload will just be the ‘value’ attribute of the button. I’ll show two cases for brevity here, coloring by ‘Square Feet’ and coloring by ‘Number of Units’:
4 Great, now Redux contains a stylesheet that shows how we want the map to look! The last step is diff’ing this with the way the map looks currently, and applying the changes. This happens back in our map React component in the “componentWillRecieveProps” method, and looks like this:
For bonus points, we can also capture the map click event and send it out as an action for whatever use you might have! We just add it inside of “this.map.on(‘load’…)” in our map component, we’ll also add the action creator for saving the initial style state here:
And thats it! This can be expanded on to add more features (delayed source loading, hooking other events like movement and zoom, etc…) but it’s really all thats required to have a very flexible React wrapper around Mapbox. Combined with the action / reducer / re-render paradigm it has allowed me to build interfaces very quickly that I can easily modify later on without having to really trace through how everything is wired together each time.
And that leaves more time for experimentation and doing work that matters!
The full project is here on github if you’d like to see everything in context with reducers, actions, and UI.
Finally, here is our completed ReactMap component, stylesheet reducer, and action creators: