Tom MacWright

tom@macwright.com

Dumb Redux

Weird plane

React and Redux have quickly conquered much of the “web application” problem space. The concepts of unidirectional data flow, Reactive programming, components, and immutable data are at the core of this change.

React & Redux are successful because they yield understandable and robust applications. They don’t solve all problems: there are still places where mutable state is useful, like gaming, or where you can’t do efficient total re-renders, like WebGL. Redux hasn’t really solved asynchronous operations - almost every system considers async to be out of scope, and async remains the hardest part of building a big application. But the principles work for the majority of web applications, where consistency is vital and modularity is the key to being productive as a coder.

So most people use React and Redux, or some similar system, to build web applications.

You might use Cycle.js instead, or Elm for an even purer representation of these concepts. That’s what I do at work with Mapbox Studio - it’s React, Redux, Immutable.js, and quite a few other libraries combined that give our small development team a friendly and safe environment for writing new features.

But this is all about applications: interactive products like Mapbox Studio or any other website, once you’re logged in. Many of those applications also have brochureware - traditional websites with static pages, blogs, terms of service, and the like. In most cases, brochureware is a separate system: Mapbox’s is generated by Jekyll and has lightly interactive pages with good old-fashioned jQuery.

Recently I worked on some of these website-parts - the installation guides for Mapbox’s Android and iOS SDKs. If Mapbox Studio is 90% logic and 10% content, these pages are the opposite: most of the work is in writing the right thing, and they need just a bit of JavaScript to tie it all together.

So I made those little toggles that let you choose instructions that use Gradle versus Fabric, or CocoaPods versus a Framework. The goal wasn’t complicated, but there’s no established true way of doing it, and the reality will always include a few extra details - like the Android SDK has a tiny checkbox that toggles directions for snapshot builds, and has two sets of toggles on the page.

Previously, we had used the :target trick to hide and show content on the page. This worked fairly well, but is brutally simple. Only one :target can be shown at a time, and its triggers must be extremely literal: links to specific fixed URLs.

To make this all more robust or simple or at least familiar to my Redux-soaked worldview, I wrote a tiny dumb Redux-like abstraction. I’ll describe it briefly, and want to push the idea that concepts are the real win, and they can be expressed in many different ways.

There’s a quick example: click through the tabs to see the parts working together. In contrast to our typical way of getting things done with jQuery, there are some really nice perks - all cribbed from the React/Redux way of doing things:

function render(state) {
  $('.js-target-count').text(state.count);
}

There are no piecemeal changes to the page: there is only one render function and it makes all possible changes to the page. This is one of the best ideas React introduced, and it tends to simplify most applications. React gives you a full implementation of the idea: there’s a render method that plays the role of a template and also updates the HTML in response to user interactions. Since this page is static HTML (or generated Jekyll), I’m only doing half of the work - still relying on the initial page content, but modifying it in a simpler way.

var state = {
  count: 0
};

State is stored in JavaScript only and in one object: this one’s from Redux. Classic jQuery applications often store state in the page itself - whether that’s in the value property of form inputs or in the presence or absence of CSS classes. This faux-React/Redux strategy instead only gets information from the DOM via events, and then stores it in a single object called state.

Also, this state has an explicit default value - { count: 0 }, and that value is rendered initially on to the page. A lot of our lightly-interactive brochureware pages rely on HTML and CSS classes to set the default page content, especially in the case of hiding certain content. Instead, this example follows the same approach as React & Redux - reducers have an initialState and the whole system is kickstarted with an empty action.

function reducer(state, action) {
  switch (action.type) {
    case 'increment': state.count++; break;
    case 'decrement': state.count--; break;
  }
  return state;
}

State is only modified in once place: the reducer. This is one function that takes the previous state of the application, in this case the previous count, and yields a new state as the result of some user intention, which is represented by an action.

<button data-action='{"type":"decrement"}'>-</button>

Actions are JavaScript objects that can configurably modify the state. This is also taken from Redux, though this specific syntax is more like Elm, I think. This is roughly the same idea as the command pattern, and it’s a nice abstraction that makes it easy to re-map, re-route, or record user input in creative ways.


If you’re building an application, use a powerful system like React + Redux to get the job done. And when you’re working on the little bits of moderate interactivity that make the light interactions of the rest of your website work, you can adopt some of the good concepts without rewriting everything with React.