During the last few years, some client-side state management solutions have gained traction and have become hugely popular in the JavaScript community. These include Flux and its grandchild, Redux, both of which were created by Facebook:
Both were introduced to try and solve the state management issue that impacted Facebook more and more as its user interface grew in size and complexity.
We won't be using Redux in this book, but nevertheless, it is very interesting to understand what it is and how it works.
Redux aims to help you write applications that do the following:
- Behave consistently thanks to the single source of truth represented by the Redux store
- Run in different environments (for example, client, server, native)
- Remain easy to test
Redux (https://redux.js.org) is, in fact, both an architectural pattern and a library.
The Redux pattern prescribes how data/state changes should flow in the application so as to maximize predictability and consistency. Simply put, the Redux pattern recommends a unidirectional data flow. We'll discover what this actually means soon.
On the other hand, the main goal of the library is to provide a predictable state container or store for JavaScript applications.
Redux also focuses on the developer experience and provides great support for debugging. Notably, it supports time travel debugging through web browser extensions. The idea behind time travel debugging is that, through browser extensions, you can explore the content of the store, replay past events, or even go back in time.
With Redux, the whole state of the application is stored in an object tree within a store, which acts as the single source of truth for the whole application. Any component within the application can subscribe to the store and be notified whenever the subset of the stored data it cares about has changed.
Here's an updated version of the schema that we discussed earlier:
With this approach, when the component in dark gray pushes information into the store, all the light gray components that have subscribed are notified of the change. This is much more efficient than the naive approach of using custom events and props. It is also cleaner than blind broadcasting. This alone makes Redux a much more interesting solution, but it doesn't stop there.
If we zoom in a bit, we can see how the state data flows within the application:
The component in dark gray triggers an action, which ends up updating the state of the application, which, in turn, causes an update of the view. This is the unidirectional data flow.
If we zoom in even further, then we can see what a pattern looks like in action:
All of the components in the application dispatch Actions, which are handled by Reducers, which in turn update the store. Also, components can observe the store and the store will notify them whenever the slice of the state they care about has changed.
This way, the application's behavior can easily be observed and the data flow becomes clear and predictable.
With Redux, the store is also immutable and read-only. The only way to change it is to dispatch an action and let the reducers derive a new state. Deriving a new state means constructing a new state object tree, not mutating the existing one! This immutability constraint is the one that makes it possible to travel back in time. By keeping track of actions and store state changes, it becomes very easy to replay events and put the application back into a previous state (simply by replacing the current state with a previous one).
When an application follows the Redux pattern, it has to implement the following:
- Actions: These describe a change to make. Each action has a type (for example, SET_USERNAME) and an optional payload. Actions follow the command design pattern.
- Reducers: These determine whether and how the state changes in response to actions. Reducers update the state tree in the store:
- The input for a reducer is the current state, as well as an action
- The output of a reducer is a new state (again, the existing state is not mutated!)
- Store structure: The store should be considered as a database that contains a tree of data that will be manipulated by the reducers as a result of their actions:
- In JavaScript, the store can be a Plain Old JavaScript Object (POJO).
So, what data should be put inside the store?
- The UI and the related state that should be kept when switching between views (for example, data that allows us to restore the state of a search form when navigating back to a previous screen)
- Data that allows us to rehydrate the store when the application starts (that is, restore the state of the application when it is accessed anew)
In any case, don't go overboard: only the state that is relevant to one component should remain local to it. The store is only useful for shared states.
As we've already mentioned, Redux can be used with Vue.js, as explained at the following link: https://snipcart.com/blog/redux-vue.