Intro to Redux
April 13, 2016
I've been playing around with React lately and enjoyed it extensively. It's scope famously extends only to user interface components. Making it an application framework requires integration of an optional architecture layer such as Redux.
It was actually the unique concepts in Redux that peaked my interest in taking up a new web framework. React-friendly things like immutability and diffing are all over the architecture layer. As part of the bargain you get interesting side effects like hot reloading components and debugging with ability to move back and forth in time.
Here's a brief introduction to Redux.
Store
The store in Redux ties your parts together. It might not look like much but the important thing to remember is that it is a single, centralizing unit. It keeps track of the entire application state and every action passes through it.
This makes it an ideal entry point for integration of smart plugins, like debugging, logging, and other so called "middleware". If you are familiar with Python, they work very much like decorators.
initialState
A well known React concept. It's no more than a plain JavaScript object representing the state of a component at the time it's initialized. During the course of a component's life cycle, attributes of the state are mutated and triggers the UI to update.
Redux uses the same concept but handles multiple components and it's state tree will determine the "sub-states" of every one of them.
Action types
One of the selling points of the React approach is the ability to overview each isolated component at a glance. Redux carries this concept to the application layer.
The application state undergoes mutations triggered by a predefined list of actions. It's common in Redux to have a module just listing every possible type of action. For a todo app it would be something like:
ADD_TODO
REMOVE_TODO
TOGGLE_TODO
This makes it possible to overview everything that is going on in an application without searching through multiple files.
Actions
The next concept is the "actions" themselves. An action is simply a function that the interface calls (or dispatches) when e.g. someone clicks a button to remove a todo item.
You decide which parameters are required and then return an object which has to specify an action "type".
export const addTodo = (text) => {
return {
type: 'ADD_TODO',
name,
};
};
If you are dealing with persistance, this is also where Redux would ask you to make a call to your API and sync your front- and backend.
Reducer
A reducer is essentially not more than a glorified switch statement who's job it is to update the state in response to a dispatched action. You are only limited to updating it without mutating it in-place.
reducer(currentState, action)-- > newState;
After each action is dispatched you end up with two version of the application state: before and after. These can obviously be diffed to optimize how much of the UI needs to be updated.
Now imagine if you saved each of the state outputs from the reducer. Each one contains enough information to render the whole UI at that point. So, that means that you could simply swap one state for another and watch the UI update to reflect the differences. This is exactly what the "time travelling debugger" is all about. Check it out - it's very cool!
Lastly; the immutability of the state tree can seem like a headache if the state tree object is big to begin with but luckily there are some nifty updates to ES6 that alleviates this greatly. Especially check out the sweet object spread operator.