From Reflux To Redux (part 1/2)
Rob Cobb, 4 Jan 2016
At Fin, we use React to build the powerful internal dashboard our operations team uses to service requests. We love the clarity and reusability that React components afford and the powerful dev tools available in the React ecosystem.
When we first built the Fin dashboard, the React community was still the wild west. Best practices and frameworks were still emerging. We picked Reflux for our frontend dataflow framework.
As we’ve grown the dashboard from handling just a few core models to handling complex interactions between many different kinds of data and events, we’ve run up against the boundary of the complexity that Reflux can handle. Fortunately for us, the React community has coalesced around Redux for managing state.
In this post, I’ll give a brief overview of the reasoning behind our switch, and dive into the mechanics of our transition between the two frameworks. I assume a basic familiarity with React and Flux, so if you haven’t seen those before, give their landing pages a quick scan before diving in here!
First, a quick overview of Reflux!
Reflux is a popular Flux implementation. Like many Flux implementations, the core concepts in Reflux-land are Stores, Actions, and Listeners. Stores keep track of the data for the application. Actions tell stores how to change. Listeners update when the store data changes.
╔═════════╗ ╔════════╗ ╔═════════════════╗ ║ Actions ║──────>║ Stores ║──────>║ View Components ║ ╚═════════╝ ╚════════╝ ╚═════════════════╝ ^ │ └──────────────────────────────────────┘
For small apps, or apps where the data from different stores isn’t intertwined very closely, Reflux works pretty well. (You can read more about the Reflux data flow model here).
For large apps with lots of state, where some functions depend on data from lots of different stores, Reflux can get pretty messy. Instead of the clean flow above, it looks more like
┌─────────────────────────>╔════════╗ ╔════════╗ │ ┌ >║ Store ║ ──────> ║ Server ║ │ ┌─────────────────┐ │ ╚════════╝ <────── ╚════════╝ │ v | │ ↙ ↘ ╔═════════╗ ╔════════╗ ╔═════════════════╗ ║ Actions ║──────>║ Store ║──────>║ View Components ║ ╚═════════╝ ╚════════╝ ╚═════════════════╝ ^ │ └──────────────────────────────────────┘
Or something equally crazy.
It also makes some bad habits easy. Instead of specifying exactly what data in the store a React component cares about, we could use the Reflux connect mixin to make sure the component updates with the new data any time the Reflux store updates! Instead of writing ‘dumb’ view components and ‘smart’ container components, as advocated by the React community, our components ended up with tons of view and behavior logic inside.
At a certain point, (probably when we were writing something with three layers of stores triggering actions on other stores), it became obvious that we needed to switch to something better. Enter Redux.
Redux is the recent darling of React development. Thousands of github stars and hundreds of add-ons in its ecosystem were signals that we should take a look. It turns out that Redux is popular for good reason.
- It has a minimal api.
- It is relatively simple to learn.
- It has clear best practices, making antipatterns difficult to write and obvious to highlight for correction in code review. Redux has only 1 store, and the reducers that define the store are pure — they have no complicated side-effects to worry about. Actions still exist, but in Redux-land, it’s much harder to write yourself into a nest of actions calling actions calling yet more actions.
What’s more, the Redux docs are clear (compared to the sparse reflux docs), and the active Redux community is friendly and has lots of support.
Turning Reflux into Redux
Even if you are familiar with writing Reflux apps and with writing Redux apps, transitioning a codebase from one and the other is far from obvious. It’s tempting to do a massive, all-in-one rewrite of the data layer of your app. If you are just starting out or have the time / capacity to hold a massive rewrite in your head, more power to you. At Fin, we were facing upwards of 30 stores to port over. That’s not a pull request I’d like to review, and would probably never happen.
So, the challenge is to make the switch incrementally. In particular, we’d like to be able to port one Reflux store at a time over to a Redux reducer, with all of the tests passing for both versions. That way, we can introduce the new style to the team over time, get incremental benefits to speed and code clarity from the code we port over, and complete the migration in a series of well-tested commits of a few hundred lines at most.
The secret (as helpfully hinted at in the Redux docs, is to write a function that converts Redux reducers into Reflux stores.
Wait, what? Aren’t we trying to go the other direction?
Well, yes! We want our code to be written to use Redux. But, we can’t change everything at once. All of our React Components are expecting to get data from and dispatch actions to Reflux stores. We want to avoid making enormous changes all at once — updating a store and all the components that get data from it or dispatch and action to it sounds like a pretty massive change!
It takes as arguments a Redux reducer and a corresponding set of
ActionCreators, and returns a Reflux Store.
This little bit of metaprogramming will let us change the implementation of a Reflux Store into a Redux reducer, without any other files changing. The rest of the app stays the same — components still see the Reflux store, even though the code implementing it is now a Redux reducer and ActionCreators. Even the tests for the Reflux stores themselves should still pass!
Using the StoreFromReducer function
Let’s take a look at the before and after view of a fairly simple store. The
SearchStore keeps track of a search value and selected item in a typeahead. The search value can change, and the index can move up and down, and both can be reset.
It’s not terribly complicated — an example that demonstrated the benefits of the switch would probably be too complicated to be useful. The rewrite is the same as it would be for a more complex store, though, so let’s take a look!
We first write reducer functions for each piece of data in the reflux store. Reducers are pure functions that transform state, given some action. As we transform our store, our reducer functions should obey a few rules:
- Their default state is the initial state from the Reflux store
- They should return the state they are passed in, unless they ‘care about’ the type of action passed in.
- Each onAction method that updated the data in the Reflux store should have a corresponding action that triggers the same transformation in the reducer.
We’ll use the redux
combineReducers function to turn these functions into a single reducer to pass into
StoreFromReducer, so each function will 'care about' only one piece of the data from the Reflux store. If there was a function in the Reflux store that modified more than one part of the store data, such as
onClearSearchInputs, each reducer function should respond to the corresponding action type. In this case, both the function for the selectedIndex and the function for the search value will update when the action type is
Our actionCreators are (as implied by their name) functions that create actions to dispatch to the store. These will be pretty simple in our case, because our store doesn’t do anything fancy. If we made server calls or called into other stores, we would have more complicated actions — probably using Redux Thunk.
All together, our SearchStore as a Redux reducer and actionCreators, transformed with our
So far, the only actual Redux library code we are using is the
combineReducers function. Even without using the library, though, this step is the heart of the switch - we've transformed a stateful, side-effect ridden Reflux store into a set of pure functions. What's more, the rest of the app can stay the same! Our components don't need to know anything about redux, yet - they still subscribe to Reflux store triggers, and they still dispatch actions to Reflux.
Thanks for reading! In the next post, I’ll go over migrating components and the setup at the top level of the app to complete the switch over to Redux.