So, what is redux?
Redux is a predictable state container for JavaScript apps. What does it mean? Let’s look at the redux data flow:
Data flow is fairly simple. Components invoke action creators via different event like onClick, action creators invoke different actions and actions come to the reducers and then reducers change the state. And the new state comes to the components. And here we are again.
An action is just a plain object, it is a payload of information that sends data from your application to your store:
Reducers describe how the application’s state changes in response. Important to know, that reducers are pure functions:
As you see, actions go to the reducer and modify the state.
So, what is the role of action creator? Let’s look at the code:
In the most trivial cases, action creator is a simple function that invokes the action. But what if we need something more? Like async actions? For that redux has middleware. Redux middleware provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. And the most simple middleware for async actions is redux-thunk:
In this case, action creator with redux-thunk will look like this:
This looks ok before we start to test it. Especially if we are using some router, cookie, analytics, different services, etc. Why? Our action creators are not pure functions. Though it is possible to test functions with side effects, I don’t like the mock hell, we can make our life easier.
So how can we improve?
The answer is Dependency injection. In my opinion, dependency injection is one the most powerful design patterns.
We can rewrite our redux middleware with dependency injection (DI):
After that action creator with DI will transform to:
Now we can control all third parties services in our action creators. And we can easily test it, cause, yeah! They are pure functions now.
But we can improve even this approach. And it’s called redux-saga.
In my opinion, it is the most elegant way to write complicated action creators. Look at this beautiful code:
Maybe for the first time, it looks a little bit complicated. But in one single function we’ve defined the whole authentication flow. Isn’t it amazing?
How does it work?
The answer is generators.
Generators are Functions with benefits.
As you see it is really simple. But simplicity hides the power. Imagine, we can wrap the generators with promises and control their flow. And generators can use promises. This library is called co — generator based control flow.
So we get the synchronous execution of the asynchronous code. This is pretty cool.
So, redux-saga did the same but with full redux integration.
Let’s look at the simple example
What happens here?
we select part of the state
then call the api method
then put(dispatch) an action
select, call, put are called effects creators. They are special functions which return effects. Effects are objects like instructions to the middleware to perform some operation. Because of that, it is easy to test sagas:
Because effects creators return special objects, we can test against them, without calling actual functions.
The most powerful part of sagas, that sagas can be daemons
In the example above, the saga has the endless loop, take listens for a specific action like CHECKOUT_REQUEST from our UI components. This action triggers checkout.
All watchers can be called from root saga which we call from the start of the application.
Let’s look at some interesting redux-saga patterns.
- takeLatest. The first one can be useful to handle AJAX requests where we want to only have the response to the latest request.
- takeEvery allows multiple saga tasks to be forked concurrently. Useful for some UI actions, when you need to process each action.
And… One more thing… Server rendering.
It is fairly simple. Imagine we have some common createStore.js module.
The last line is important. In this case the client side will look like:
And the server side:
Where match is part of react-router. We call static method fetchData of our react components, then wait when saga will be done using saga.done.