We can look back to 2015, or as many know it, the year of “React fatigue.” For a while, everyone was happy with Google’s Angular.JS framework, and the sense of rigor it brought to the wild west that is frontend programming. However, many grew dissatisfied with Angular for a variety of reasons.
One of the top issues many developers have with Angular is that it is a very opinionated framework; there is an angular way to do everything. When Facebook’s React came to the scene as a relatively small “view” library, many felt it was a breath of fresh air. As React has such a small scope, it was easily comprehensible, and developers would roll their framework consisting of React and other libraries.
This is where things started to turn sour, and an opposite but equally painful problem occurred: Too many decisions and unsolved problems. State management was one of the biggest areas that lacked proper solutions.
In React the only state management primitives provided are immutable props and mutable state. As state is harder to reason about, React encourages you to use state ideally sparingly and focus your efforts on props.
If you have a component that requires a change, in most cases, you will throw that component out, and generate a new one with the required props. Those with knowledge of React’s virtual DOM will know there is a little bit more going on under the hood than this — in order gain performance optimizations — but conceptually this is what happens.
As such, React developers will isolate state higher up in their component hierarchy, and pass it down as props, letting the state flow from the top of the tree to the bottom. In many cases, this is quite elegant and frankly overlooked by developers eager to dig into the full React ecosystem.
Problems start occurring when the component tree gets tall, and you have components that are far from each other on the tree, and one component is not a descendant of another, AND both components depend on the same bit of state.
To put this in simpler terms, let’s say you have a navbar and a component that displays new messages. Inside that navbar, there is a component that will indicate if you have a new message. In this scenario, both the navbar component and the messages component will depend on the same state. Here, neither component is a descendant of the other, which makes managing the state more challenging.
To avoid having “multiple sources of truth” the correct thing to do in this case would be to store this shared state in the nearest common ancestor — or any mutual ancestor — and pass the state down as props from there.
For small to medium apps, this is an okay approach, but it is cumbersome with large applications, and there can be performance implications of having to locate the state higher up in the component tree.
For those who haven’t encountered this issue for themselves, this issue is one of the primary motivators for Flux, Redux, Relay, and related data management tools. They all take a different approach to solving this problem, but fundamentally they are all attempting to fix the same issue.
Instead of passing this state from a component down to the hierarchy, all these tools move that state to an external store or stores, depending on the state management tool. Then, while the mechanics are extremely different in all of the mentioned solutions, in each of these solutions components will declare a dependency on the store(s) they are interested in.
Whenever the store changes, these components will repull the data, and rerender their children. These “container” components as they are frequently called will pass the data from the store down to their children as props. In all of the above solutions, the role of vanilla React’s component state is marginalized.
An Army of Fluxes
Facebook’s Flux is the original state management library for React, although it is more of a pattern than a library or framework. While there is an NPM package for Flux provided by Facebook, its offerings are quite sparse, and most of the heavy lifting is left to the application developer.
One of the main ideas in Flux is that state should be publicly readable, but only be able to be modified in a loosely coupled way. Instead of React components calling some sort of setter method for the stores, components will instead create actions. All of the stores in the application will receive this action — via a centralized dispatcher — and then the store would determine for itself if it should update its state in response to that action.
There was too much boilerplate, and too many layers of indirection in the vanilla Flux implementation. This has lead to an abundance of Flux implementations.
This architecture decomposes complex UI state dependencies to a simple system. If two UI components are interested in the same state, both will listen to the same store. Whenever there is an action that causes the shared state to change, both of these components will rerender in response to that change. While the underlying principles are fantastic, the implementation details of vanilla flux are a little difficult to wrap your head around. The centralized dispatcher seems unnecessary at first, and there is a lot of code that goes into making flux stores. All in all most developers felt.
Too Much Flux
There were too much boilerplate and too many layers of indirection in the vanilla Flux implementation. This has lead to an abundance of Flux implementations.
Many of these libraries have come and gone over the last year, but the kind-of-a-flux-but-not-a-flux Redux has emerged as a de facto standard in React applications. Instead of using dispatchers and event listeners, Redux relies on functional programming and immutability to power a singular store.
Another powerful state management option that is inspired by Flux, but detours quite a bit, is Facebook’s Relay framework. Relay relies on GraphQL and colocating data dependencies with the component. Be on the lookout for part two of this article where we will be taking an in-depth look into why these particular solutions are winning so much developer mindshare.
Feature image via Pixabay.