The idea for this post is to show you Redux-Saga behind the scenes. Once you finish this post, you’ll have a complete understanding about the library, how it works and why it does what it does.
I invite you to join me on this trip, not just to acquire some basic knowledge about Saga, but to fully understand a library that is being used by a lot of React projects nowadays, and it’s very useful in order to make your component’s code cleaner and easier to maintain and test.
Part I → Generators? Sure, let’s talk about them!
Part II → A walk through the Saga
So… What is redux-saga?
We can come up with two different definitions to answer this question; the first one from the official Saga documentation (it’s very important as a first look), and the second one, based on some research I did across the internet.
- From github documentation: Redux-saga is a library that aims at making application side effects easier to manage, more efficient to execute, easy to test, and better at handling failures.
- From blogs and rest of the internet: Redux Saga is a middleware library used to allow a Redux store to asynchronously interact with resources outside it. This includes making HTTP requests to external services, accessing the browser’s storage, and executing all kinds of operations. These operations are also known as side effects.
Ok, let’s pause for a minute. We have a couple of concepts here which we need to understand in order to continue digging into the library’s core. We read about middlewares and side effects, but do we really know what those definitions are in this redux-saga environment? Let’s review them.
What is a side effect?
Any kind of dirty work that we need to perform in our application should be considered as a side effect. Fetching data from an API, validating data forms, accessing browser cache, logging information, etc. Any of those tasks is considered as a side effect and we shouldn’t do them inside the code of our components.
Any kind of dirty work that we need to perform in our application should be considered as a side effect.
A component should only be concerned with what to render and how to render it. This is an important rule you should always keep in mind when you develop a component in React. Your components shouldn’t involve itself in fetching data from an API or how to log information to the console. Those are things you want to leave for side effects managers, like Saga.
A component should only be concerned with what to render and how to render it.
So, let’s go with the other concept we need to understand before continuing with this Saga post. Yes, you’re right! I’m talking about middlewares. But what is middleware? Well, we’ll see…
Middleware? Let’s talk about that…
In software development, middleware is a piece of software that runs between two different softwares layers, and has access to some particular aspects of each one of them.
In a Redux environment, a middleware is a piece of code that runs between our application and the Redux world or, to be more specific, between the dispatched action and the reducer of that action.
Redux middleware has access to the global store, to dispatch functions and sometimes to reducers as well.
Redux Saga as Middleware
When we create a new store in Redux, we’re able to initialize a middleware too. That means we can start to run software between our application and the Redux environment.
In this particular case, Redux Saga will run between the dispatched action and the reducer of this action.
Saga is able to dispatch actions, access the full store, and run/stop once an action has been dispatched.
When do we start our Saga world?
Our Saga is initialized when we create our store on Redux. Here is where we say: I want to use Saga as middleware.
From this point, our Saga environment is ready to go inside the Redux module of our application.
Piece of code with store creator
The third parameter of the createStore function allows you to specify your store enhancers such as middleware functions, third-party libraries for persistence, etc. In this post, we’ll only cover middleware functions and not worry about other kinds of enhancers.
IMPORTANT: It’s possible to add more than one enhancer with the compose method provided by Redux. This method allows you to set multiple enhancers at once, and it will return a function with a composition of all the functions/enhancers you’ve set.
One of the most common enhancers (which, by the way, Redux supports natively) is applyMiddleware. This enhancer allows you to inject as many middleware functions as you want in your Redux store.
So at this point, you may be wondering what the difference is between our application with and without Saga, right? Let’s take a look at the data flow in a React application based on Saga.
What is the normal flow of data when you add Saga as middleware?
On a React Redux based application, you’ll find a View based on React components which users can interact with. This interaction will eventually launch (or dispatch) actions to the reducers. Before discussing reducers, let’s pause at the middleware (Saga). This middleware has access to the entire Store, to the dispatch method and to reducers as well.
At this point, we can call the API and fetch some data, or access to Local/Session storage and get or set an item, or even dispatch another action (which isn’t allowed inside a reducer).
Once our middleware has completed the actions, it finally passes execution to the reducer (if the reducer for the action that was called exists) and performs any changes on the state. That will re-render our view linked to this state and the execution terminates.
This middleware has access to the entire Store, to the dispatch method and to reducers as well.
Basic concepts of Saga
Now that we know a couple of concepts closely related to Saga a bit better and understand what the data flow in a React/Redux/Saga application looks like, we can continue looking into the major aspects of this library and digging into the core of it.
Before moving on with this post, we need to talk about generator functions. We haven’t talked about them yet, but they are a really important thing you need to understand because the whole saga library is based on generator functions.
Have you heard of generators?
If you have never seen a generator, I suggest you go check out this link before continuing. You’ll find everything you need to know about generators and how they work there.
In order to “listen” to the actions that are being dispatched, Saga provides us with a few methods that help us to decide how we want to intercept these actions. These methods are called “effect creators” and are really useful when we want to have control over the way we listen to the dispatched actions.
The most commons ones are:
- takeEvery: a task will be executed every time an action is dispatched.
- takeLatest: if an action is dispatched more than once AND the previous task for its action is still in progress, Saga will kill the previous task and start another one. This is useful when we don’t want to perform a task many times for the same dispatched action.
- put: replaces the dispatch method for Redux.
- call: execute another method (like an API call, for example).
Those are the most common ones. I invite you to take a look at the Redux Saga documentation, where you’ll find many other effects you can use to handle your actions.
Our first look into a Saga
So… let’s take a look at an example of Saga, in this particular case, to fetch a user by id…
Fetch Car instance by id with Saga — Example
On store.js there is not much to explain, we’re just applying our middleware to the Redux store as we explained a couple of sections ago. saga.js is where the magic begins, so take a moment to analyze that.
First, we’re exporting a generator function as default. This function is going to be executed every time we dispatch an action with type “FETCH_CAR”. As you can see, this time we decided to handle the action with the takeLatest effect. This means that every time we dispatch the action, Saga will kill the previous task and start another one. And what task am I talking about? Well, the one that is being passed as the second argument of the takeLatest effect, fetchCar. This is the task we want to perform every time we dispatch this action in our React application.
Now, we should analyze the code of this particular task. First, we see this task receives the entire action as a parameter, so it has access to the type of the action but a payload as well. From there, we get the car id (remember, in this example we want to fetch a car by its own id).
So, once we have the id, we fetch the car with an API call. Look at the yield command there. Once the response of the API call is in our car variable, we call the put effect to dispatch another action (“FETCH_CAR_SUCCESS”).
All this code is wrapped inside a try/catch block, in order to handle any possible errors we could have whether with an API call or any other code inside our task.
Finally, we also have a put effect to dispatch the failed side of the request so that we can notify our Redux environment that something went wrong while fetching the car.
Once either FETCH_CAR_SUCCESS or FETCH_CAR_FAILED have been dispatched, the regular Redux data flow is run and everything returns to regular execution (dispatch -> reducer -> store -> view) unless, of course, you have another Saga listening for those actions! ;) ;)
And that’s it about Saga! Thank you for reading the entire post! Hope you’ve learned a thing or two about Redux Saga and its great power.
We’ve covered all the basics aspects of Redux Saga, and now you should have a better understanding about this excellent library. Hopefully you will be able to include Saga in your Redux application and start performing some side effects on your dispatched actions.
Hope you have enjoyed the post as much as I enjoyed writing it.