The State Management Goes Wild
This is the first article of the series where we will try to find out if there’s a place for Redux on the other side of the fence.
Even though this series is base on the assumption that you are more or less familiar with the Redux, but don’t worry if not, as we will go over necessary concepts first.
Once we are confident with the Redux as a React state manager, we will be exploring how we can use it as a back-end state management and state distribution tool and gradually build the conceptual design. Last, but not least we will get our hands dirty and build application with Redux on the back-end.
If you are super-comfortable with Redux, feel free to start with the desert.
Very brief History
Redux appeared as a proof of concept during preparation for the React Europe conference, back in 2015.
I was trying to make a proof of concept of Flux where I could change the logic. And it would let me time travel. And it would let me reapply the future actions on the code change.
Very soon Redux got huge popularity in the front-end community, it is simple, easy to follow library for the state management. Redux makes a lot of complicated tasks trivial.
State Management
To understand what Redux can bring to the table we’ll start by looking at what pure React can offer first.
React doesn’t come with the Redux out of the box, and there’s a reason for that. Most of the time you probably won’t need it. There is a React way of managing state distribution and state dependency. In React you can propagate state up to the top-level components and make it in charge of distribution and dependency management. The state flow is single-directional and easy to manage.
Think of the React application as of simple water-filter. Where the state is water, each layer is a component. We pure the water in the bottle, water passes through each layer consistently, each layer takes whatever it has to take and letting the water flow to the next layer.
I hope the idea is clear, but why and when do we need Redux?
You’ll know when you need Flux. If you aren’t sure if you need it, you don’t need it.
– Pete Hunt (one of the first React contributors)
We can apply the same rule to the Redux. If you aren’t sure if you need it, you don’t need it.
Once you have a lot of data moving here and there and top-level React component state is not enough to distribute it. It is time…
Redux allows you to move out the “state source of truth” from the top-level component into a separate object. And the only way to change the current state is to interact with this object. This object called Store.
Immutability
Understanding immutability is very important, for proceeding with the Redux. Because in Redux state is immutable 🌳.
The idea behind the immutable data is simple, you cannot modify it. Like natural numbers. 2
is a natural number and whatever you do, it won’t change 2
. You can operate on it and, let’s say, add 3
to it, but the result will be another natural number, 5
. 5
is another natural number.
Why immutable data is good? Because you can pass it around and not worry it will be changed in a way you don’t expect it to. It becomes even handier in the distributed multi-threaded environment, but that’s another discussion.
Immutable by convention
Immutable data is a key aspect of any Redux-based architecture. Even though it is a key aspect, there’s no real enforcement, it is so-called immutability by convention. I think immutability by convention is not a thing… If an object can be mutated, it will be mutated, just a matter of time… I highly recommend stepping away from the immutability by convention once the data-state tracing becomes uncomfortable.
JavaScript has some data structures provided out of the box. There’re an Object.freeze() and const which kinda allow you to have some immutability. Yet using them is not very efficient from the memory perspective, because every operation will require you to copy your data from one place to another. Quite expensive, taking into account the fact that every copy will require extra memory allocation, copying and garbage collection.
To keep things error-prone we’ll need something that will enforce immutability and manage memory efficiently. The immutable.js does exactly that. It is a library with a collection of immutable data structures. Immutable JS uses persistent vectors to perform insertions, merges, etc. It removes necessity in copying and caching of the data.
Pure functions
Mathematical functions
The immutable data is a key aspect of the Redux design and we need to respect it whether we use it in a conventional manner or by enforcement.
But how do we deal with immutable data in a way that we can still benefit from it?
Let’s get back to the natural numbers example, we agreed that natural numbers are immutable, and we tried to add 2
and 3
, which resulted in 5
. This can be written like 2 + 3 = 5
. To make it more generic we can describe it as the mathematical function, like this f(a, b) = a + b
. It is predictable, it does not introduce any side-effects, for 2
and 3
it will always return 5
.
Pure functions are mathematical functions. And pure functions work very well with immutable data, there’s even a whole programming paradigm that takes those two as its foundational platform, you might know it as functional programming.
We spoke about the state and its immutable nature in the Redux. We also spoke about the Store and how it secures a state from any unauthorized impact. And finally, we found that pure functions are a very convenient way to operate on immutable data, which allows keeping transparency and predictability in place.
Reducing functions
The only way Redux Store allows to operate on its state 🌳is with actions. Those are special “instruction” objects, very similar to what Commands are in CQRS or Events in Event Sourcing. They define an action/operation that intended to be applied to the state and carrying a necessary payload. Add an item to the basket is an action where the item you want to add is a payload.
Redux uses a special type of high-order functions to process actions, the reducing function. The reducing functions is not a new concept in the JavaScript, the array.reduce(reducerCallback, initialValue) function reduces an array to a single value. It uses a special user-defined reducer callback which is executed recursively.
(accumulator, currentValue) => nextAccumulator
Similarly, Redux Store will use a _special user-defined _reducer callback which will be executed synchronously when an action will be dispatched. As you might guess, reducer must be a pure function. It takes a state and an action and calculates the next state.
(state, action) => nextState
Interception and Subscription
Redux uses Middleware pattern to provide integration points before and after an action is dispatched. You can stack multiple middleware functions. You are in charge of whether the execution chain continues or not. You can control it with the next(action)
function.
Another integration point is Redux Listeners, which located closer to reducer callback than middleware. Listener functions are executed one by one right after reducer. Listeners don’t have any control over execution flow.
Keep in mind, it is simple
Redux doesn’t force you, it empowers you by providing a framework. You can have multiple stores, mutate state, create side-effects in your reducing functions and finally, you don’t have to use it as a state source of truth at all.
Redux is not rocket science 🚀, it is just a Flux pattern implementation and if we strip all the error checks, comments extension points from the createStore.ts it will fit into 20-30 lines of JavaScript code.
|
|
Few more things are happening in the Redux and it slightly differs from the Flux, however, we won’t go in too deep. We covered just enough to move forward to the next chapter and design the use-case for the Redux on the backend.