Ronaldo Vitto Lewerissa

Software engineering learning documentation.

Using Redux Saga To Manage Your Side Effects

If you’re using Redux anytime by now, you’ll realize as your codebase goes large, so does your side effects. And by side effects I mean data fetching, or any async processes that needs special treatment.

Previously people would suggest to use redux-thunk, which basically is an action creator that returns a function, in which it will be invoked in your middleware. But it has it’s own challenges:

  • it’s not testable
  • no concurrency
  • the bigger the codebase, the more complicated it will be

Having Redux Saga as one of your dependency will tremendously makes life much easier as it abstracts complex async operation beneath the library’s core.

There are several prerequisite before proceeding to look for Redux Saga, you need to understand about generators and promises, better yet, async-await style.

This is because Redux Saga uses generators. Implementing a saga requires you to keep yielding stuff.

I suggest you to read the following beforehand:

Introduction

Redux Saga === Middleware

Yup, Redux Saga is just another kind of middleware that will intercept any of your dispatched action before handing it over to your reducer.

Getting Started

Because it’s a middleware, you need to specify your saga when creating your Redux store.

import {  
  createStore,
  applyMiddleware,
} from 'redux'
import createSagaMiddleware from 'redux-saga'  
import reducer from './reducers'  
import mySaga from './sagas'

// create the saga middleware
const sagaMiddleware = createSagaMiddleware()  
// mount it on the Store
const store = createStore( reducer, applyMiddleware(sagaMiddleware) )  
// then run the saga
sagaMiddleware.run(mySaga)  

This code is taken from the official documentation.

Watcher and Worker Saga

Generally you’ll have two kinds of saga:

  • Watcher Saga

  • Worker Saga

Watcher Saga watches for any incoming dispatched action.

When it receives one, it will spawns/initiate a worker saga, often referred to as a task, which will do the work.

I like to think a task as just another layer of middleware, but with a special ability of pausing and resuming.

Therefore, you can initiate multiple of worker saga/tasks at the same time, each will work concurrently.

Effects

You’ll see from the preceding code there’s a magic call  and put  invocation. call *and *put doesn’t directly invoke a function that does the work, instead it returns an instruction for the middleware to execute.

Here’s what it looks like:

{
  CALL: {
    fn: Api.fetch,
    args: ['./products']
  }
}

This kind of pattern is seen in a lot of libraries, including React’s React.createElement().

Redux Saga call this as an effect.

Call and put is two of several other effects Redux Saga introduces, but frankly you won’t need all.

Combining it all

Here’s what a typical watcher and worker saga would look like:

// defining your watcher saga
function* watcherSaga() {  
  yield takeLatest('FETCH_ALL_USERS_DATA', workerSaga);
}
// defining your worker saga
function* workerSaga(action) {  
  yield fetchResult = call(httpRequest);
  yield put({
    type: 'FETCH_RECEIVED',
    payload: fetchResult,
  });
}

Take a closer look on mySaga from the previous chunk of code, and that will be your saga’s starting point.

You’ll generally will always start with a watcher saga, it means that mySaga will be a watcher saga.

On this sagaMiddleware.run(mySaga) line of code, Redux Saga will directly runs your mySaga.

And what mySaga will generates is an instruction for the middleware!

So from the previous example, takeLatest will generate an instruction.

What does this instruction says?

It says to wait for the latest dispatched action FETCH_ALL_USERS_DATA, and when that action is dispatched, initiate the specified worker saga.

You can build your watcher saga like this:

function* watcherSaga() {  
  yield takeLatest('ACTION_A', workerSagaA);
  yield takeLatest('ACTION_B', workerSagaB);...and so on
}

Redux Saga flow on sagaMiddleware.run(watcherSaga) call:

  1. watcherSaga is called.

  2. watcherSaga will returns an instruction for ACTION_A.

  3. middleware receive the instruction then continues to run watcherSaga.

  4. watcherSaga returns another instruction for ACTION_B.

  5. middleware receives the instruction then continues to run watcherSaga.

This whole process will stop whenever a blocking effect is reached, commonly known blocking effects are:

  • call

  • take

Call returns an instruction to the middleware to call the given function (which has to return a promise). The middleware will wait until the promise resolves and then continue to run the saga.

Take returns an instruction to the middleware to wait for a specific action before proceeding the saga.

What I was just elaborating what happens when sagaMiddleware.run(watcherSaga) is called, which in essence, just to provide as many instruction to the middleware.

You can deep more in the official documentation here: http://yelouafi.github.io/redux-saga/

Or even try yourself the library which you can find here: https://github.com/yelouafi/redux-saga

Written by Ronaldo Vitto Lewerissa

Read more posts by this author.