State management is a must if you are developing a huge app, and using proper state management can be a real lifesaver. On React, Redux is our lifeguard, and on Angular, we have NgRx for this role.
I think, on the architectural level both have similar boilerplates and if you grasp one already, your learning curve for the other will be smoother. What I really like on NgRx is not the library itself but RxJS, which is really the most amazing library for handling the streaming events.
While I was investigating on Redux and NgRx, I realized that there are hardly any people who use Redux by itself without lots of helper libraries, but on the other hand the case is not similar for NgRx as it provides all in one solution in most of the cases. So I decided to make an experiment to combine RxJS and all Redux helper libraries for creating a super duper state management library for React with similar fingerprints like Redux.
At this point, the recipe was obvious: Firstly, I reimplemented Redux with RxJS and added some middleware salt on top of it and it’s ready to serve! So with this post, I will try to explain how I did it.
Hint: I released the alpha version of the resulting library already and you can check it here.
Step 0: Deconstruct Redux and Related Libraries
Redux provides mainly a createStore function which obviously creates a store. This createStore takes a reducer, initial state, and an enhancer.
Reducer is a function which takes a state and returns a state. Quite simple, right? But wait a minute, I will need more than one reducer, which means I will need a helper function to combine all reducers into one big fat reducer.
For a similar purpose, Redux has combineReducers function which is very handy. An initial state is an object which may be an empty or undefined state at all. Even simpler! The enhancer is a little bit weird which has the following signature:
(createStore) => (reducer, initialState) => store
Again I will need more than one enhancer and again I will need a helper function to combine all enhancers into one which is called applyMiddleware.
When we run createStore function it returns an object similar to the simplified object below:
getState: () => state,
subscribe: () => unsubscribe,
dispatch: (action) => void
getState returns the current state. Subscribe returns an unsubscribe object but if I’m planning to use RxJS I can use its own subscription and unsubscription. As a result, I will have a simpler implementation. Dispatch functions actually pass the action to the reducer. And again RxJS will become handy here which has its own dispatching mechanism.
So, I have created a TODO list to achieve my goal:
- Implement `createStore(reducer, initialState, enhancer) => store`
- Implement `combineReducer(structuredReducersObject) => reducer`
- Implement `applyMiddleware(createStore) => (reducer, initialState) => store`
Step 1: createStore with the help of RxJS BehaviourSubject and operators
At any time a store consumer calls getState, store should return the current state back. If I use BehaviourSubject as state source the implementation of internal store state will be easier since similar behavior from RxJS observables belongs to BehaviourSubject by calling BehaviourSubject.value.
This is how it’s done:
I will rephrase what I did here in HUMAN words. In the createStore function, I created a Store with given reducer and initialState. If there is an enhancer I pass a fake createStore function which will return already created Store, as a result, it will return enhanced Store. Then I dispatch the first action and return the created or enhanced store. So when the createStore function is called, we can use the store reference in our app. Maybe this reference can be passed to some react context provider.
As you can see, the most complicated part is the select function and it is not a must-have feature at all. I added this part to have an interface such as reselect. As I can understand so far, the subscribe function of Redux is not a widely used feature of it but react-redux library uses it to get the updates and rerender the component (if anybody uses it directly please comment it below, because in my experiment I couldn't find any use case for my subscribe function).
So, the createStore function so far should be able to work for one reducer and one enhancer, which means the backbone of my RxJS based Redux library is ready to be named: From now on I will call it RxJs + Redux = RxDx.
Step 2: How to create a combineReducers function?
When I checked the combineReducers source code from Redux, I realized that it takes an object (which can be nested) of which the keys have a reducer and as a result returns a bigger reducer.
Let start with the following initial code:
For now, I will avoid error prevention and error handling (I will trust whoever uses this function). The idea here is to loop over the keys of reducers object and to call the attached reducer with related state part and the action.
As a result, all reducers will update only the related part of the state and I will combine the results in the same structure and return them. While I was trying to code this function I noticed the name of the function has the solution itself: Array.reduce.
Step 3: The Magical applyMiddleware function
If you take a closer look into the Redux implementation, the biggest magic happens with only one function named compose. What does the compose function do? Actually, it is pretty simple as it takes a list of functions, wraps each of them by passing the second wrapped one to the first, the third to the second and so on, and finally returns this weird nested function:
As you can see above, the functions a,b,c,d,e becomes A(B(C(D(E(…args))))). This behavior is crucial because all enhancers should be able to be called on each action and all of them should be able to alter the action or dispatch a new one or even delay the original one. There is one more restriction to enhancers as they should follow the signature below:
// sample enhancer signature
const enhancer => (createStore) => (next) => (action) => state
Finally, I ended up with the following function by omitting the error handling and error prevention parts:
This is the core part of RxDx and I am planning to explain it further how to connect the RxDx to React components with a follow-up article to this one.
With the follow-up article, I am hoping to shed some light into the inner structure of react-redux and then I will move forward to react-observable, then reselect libraries.
If you are interested in RxDx, and enjoyed the article so far, please don’t forget hitting the clap (you can clap up to 50 times). If you share your ideas and feedback below, I would only be glad! Thanks for reading.
For Part 2 follow the link: