What is it?
Flux is not a framework or library (although there are plenty of implementations out there). It’s an architecture for handling state and data flow in ReactJS applications designed by Facebook.
The pattern is made up of:
- Actions – Payloads which contain the data and describe the change/event which has occurred
- A Dispatcher – Receives actions and emits them to registered listeners
- Stores – The keepers of application state
- Controller/Container Views – React Components which connect and listen to stores to access data and pass it down via props to child components.
We added Flux to our application after writing a mess of code to asynchronously fetch data client-side. Despite having heard of Redux, other teams at REA were already working with Flux so we opted for the familiar solution.
We were very happy with how Flux helped clean up our components, by separating them into “dumb” presentation components and “smart” containers. Until we felt the pain.
We use feature toggles to hide/show components that aren’t quite production ready. We were using a Flux store to manage the on/off state of these toggles. After testing our toggle implementation locally and a few times in production, we were happy to roll it out for use on newly developed components.
The keywords search field was added to the mobile site filters page, behind a feature toggle. After a few hours in production, some of us began to occasionally notice the page was re-rendering and flickering. We were using a combination of request cookies and query parameters to determine whether a feature should be toggled on or off. Because of this, we suspected the response from our servers was being cached.
Daniel Stankevich and I set out to prove our hypothesis, leading to this realisation:
Like many other Flux implementations, ours was using singleton stores which are re-initialised client-side. This lead to requests “poisoning” servers. Once client-side, because the toggle was never explicitly turned on, the store would be cleared and cause the re-rendering.
During our extensive Googling of our Flux issue, we came across the Redux documentation. This part caught our attention:
To send the data down to the client, we need to: create a fresh, new Redux store instance on every request
Redux is and isn’t a Flux implementation. The core principle of propagating actions to stores which hold the state logic is the same, however, there are two key differences:
- There are no Dispatchers and, therefore, no event emitters
- Redux is built out of composable, pure functions
- Flux allows you to mutate and go crazy with the data in the stores
- Redux assumes that the data is never mutated
While switching to Redux wasn’t our only option, a few of us had been wanting to try it out for a while, so we decided to time-box our attempt.
Thinking it would be best to learn as much as possible about Redux before touching a keyboard, we powered through the awesome Getting Started with Redux egghead.io course by Dan Abramov (Redux’s author) in ~2 hours. By the end of the day, and a code diff of +30 | -418 (and the wonderful +- 2,000 of npm shrinkwrap…), we had swapped out Flux for Redux \o/
Why so easy?
We even managed to migrate our biggest, scariest store at the same time.
Two things saved us.
The first was having adhered to the following two principles whilst building Flux stores.
- Read only state – “The only way to mutate the state is to emit an action, an object describing what happened”
- Changes are made with pure functions – “To specify how the state tree is transformed by actions, you write pure reducers”
Both of these points appear in the Redux documentation referred to as the Three Principles.
The core of both Flux and Redux stores is the reduce function. So, while the Flux boilerplate disappeared, the most important part of the store remained untouched.
The second thing that saved us was a clear separation of concerns between containers and presentation components. This divide meant that aside from updating where the containers were fetching data from, our components remained untouched.
Our ability to replace one of the core libraries of our application was quite the win. It was a testament to the effort the team had made towards separating concerns and responsibilities. Waiting to adopt new libraries and feeling the pain has given us a chance to fully understand the pros and cons, and make measured decisions. Eventually we discovered our need for Reselect… Stay tuned for that instalment.