redux-saga is so far the best library for managing asynchronous workflow in redux. I started using simple redux-promise, then moved to redux-thunk only to finally settle with redux-saga. In few cases, it leads to a bit more complicated code than using redux-promise directly in Component, but in overall it makes the code more predictable. Also, readability counts™!
The first thing I noticed after few days of using redux-saga is that some parts of sagas are very similar to each other. Fetch data from API, catch an error, dispatch successful action with data if everything is ok or dispatch error when something fails. The only thing that’s different is API call and action.
Let’s write a simple factory which returns our saga:
entity has two actions:
entity.request is dispatched from Component and contains parameters of fetch request in the payload (e.g. object ID, any filters, search, etc.)
api is a function which receives parameters from the
entity.request action and performs the API call:
The core of our saga lives inside
fetchSaga and can be easily tested and reused. Less code, fewer bugs, but we can go even one step further.
Use case: network failures
Imagine we have several sagas which fetch our data. Now we want to add new functionality to our fetch workflow — that’s very simple, we just need to extend
So, let’s suppose we want to detect when the network is down and resume our saga when the network is up again. All we need is one simple saga and add few lines to
networkAction are defined in a different module which handles network status and periodically pings server if it’s back again (it’s a pretty cool example of a complex workflow in redux-saga, so check it out).
reloadEntity waits until the network is up again and then triggers request again which triggers the whole
fetchSaga with original parameters. I’m not sure if it’s safe to use it for unsafe requests like POST, DELETE, but for simple GET requests it works great.
Factories are functions which returns configured sagas. Sometimes we already have a saga, but we still need to change its behaviour — that’s when decorators are useful.
Use case: authentication
Suppose we have some API endpoints which requires a user to be authenticated. There’re dozens of places where we can check if a user is authenticated or not, but we can also do it centrally in sagas. All that we want is decorate our sagas with
Now we can add a simple check that user is authenticated at the beginning of our sagas:
const createProject = authRequired(sendSaga(createProject, apiCreateProject))
sendSaga is similar factory to
fetchSaga, just made for POST requests.
Obviously, we still need the same validation in our backend, but now our users are at least redirected to login page when they try to perform such action.
Sagas are great that can be composed together. All these little pieces can be written and tested separately and then combined together into the final saga.
Also, if you aren’t still sold to redux-saga, take a look at the example of handling network failure. I guess this would be still possible with redux-thunk, but test such a saga would be really painful.
What’s your favourite factory or decorator?