In this article we will deep dive into one of the most popular state management library for UI applications i.e Redux. Despite containing simple APIs, it is very hard maintain redux or react-redux saga while building large-scale applications.
We will explore how Redux is implemented. By implementing our own version of Redux, we will be implementing core APIs.
For understanding the concepts deeply, we will be creating a simple to-do application built in React and will be using our own version of redux for state management. The complete code and working demo is shared at the end.
Redux core fundamental is that there should be a single data model(state) and application ui should be function of state, which can be stated as-
ui = f(state)
Firstly we will start implementing our version of store.
This is the fundamental of store, to store the state of the application.
Now we will have to add some methods to call the reducer to update the state and a method to subscribe to state changes.
That’s a lot of code, we will understand each thing here.
There are two important functions which this store exposes-
- Dispatch: It takes the action fired from the ui and calls the reducer with the action and stores the new state which is returned from the reducer. Along with this it calls all the listeners which we have registered
- Subscribe: This method is to subscribe event listeners, which would be called whenever the state will be changed.
Let’s now understand what reducer offers. Its work is to simply return the new version of state. A reducer is a pure function.(Immutability)
The code is fairly straight, it is taking the action raised and returning the new state.
We have implemented the core of Redux. We can look how our app functions here (sandbox link).
The app works fine, however for each new component we add, we have to repeat a lot of logic for connecting a component to store.
To create an abstraction, we will be using a higher order component pattern.
We will be creating a hoc, which will return a component connected with store.We follow the same terminologies as react-redux library and name this hoc as Connect. Let’s look at the code.
Let’s understand this pattern, connect is a function which returns a function which further gets called with the component provided, which in turn returns the connected component, by following this pattern we no longer need to repeat the logic of connecting the component with the store.
This creates our own version of Redux. Now we can use this to understand and experiment with the redux APIs deeply, especially from a performance point-of-view.
You can check the fully functional application here.
If we look at the connect function, we will observe that it gets called every time there is some attempt for updation of state through reducer.
Here is a big performance trade-off, suppose your component is only dependent on a small chunk of state, but it will be called even when that chunk is not getting updated to check whether the state being used in the component is changed or not. Hence there will be a number of unnecessary calls in a large application.
There are some patterns which we could follow to improve-
- Using abstraction in React wherever possible: We should try and use higher order component pattern by creating hoc, and connecting only them to Redux we can prevent unnecessary rerenders.
- Subscribe To Node method in Store: We can write a method which will subscribe to particular node of state and will only call its connect when that particular node have changed.This will require modification to redux code ,or using your own version of redux
- Using Normalised State Object: React Redux takes time to compare nested state object, hence we should keep our state object as normalised as possible. Using normalizer could be helpful here.