Redux Saga

2019-07-16

Redux Saga is a middleware library that is used to handle side-effects from asynchronous actions in Redux. It is way more complex compared to Redux-Thunk There are trade-offs for both Thunk or Saga, so we can not say that Saga is better than Thunk, it depends on each use case.

Intro to Saga

A saga is actually a story created to handle all logic related to side-effects in asynchronous actions. It has special abilities to start, pause or stop the functions. These abilities are based on the new ES6 feature called generator A generator looks just like a normal function, except it has the word yield inside it. With this yield, we can pause, restart or resume the function as many times as we like. You can read more about generator functions in my other blog post here

Differences between Thunk and Saga

With Thunk we can create an action that returns a function, but in Saga we return an object. In general, Thunk is often used in a simple or small scale app, whereas Saga is used in a more complex and bigger app. This is because as the app is getting larger or more complex, the actions inside Thunk can get nested and messy.

The code below is an example usage of Redux-Saga:

import { takeLatest } from 'redux-saga';
import { call, put } from 'redux-saga/effects';

import validateLogin from './actions';

function* loginFormSubmit(payload) {
  try {
    const result = yield call(validateLogin, payload);
    yield put({ type: 'LOGIN_SUCCESS', payload });
  } catch (error) {
    yield put({ type: 'LOGIN_FAIL', payload });
  }
}

function* watchSaga() {
  yield* takeLatest('ON_FORM_SUBMIT', loginFormSubmit);
}

export default watchSaga;

From the code above, we run watchSaga as a watcher saga, we yield takeLatest inside it.

takeLatest is a helper function that has a task to listen or watch for ONFORMSUBMIT action dispatched and then it will run loginFormSubmit. put and call in the code above is called Redux-Saga effects, both are plain JavaScript objects that carry instructions to be executed by the middleware. _call* is like its name, call a validateLogin with payload as the given argument, whereas put, will dispatch an action to the Store In the code above, if login validation is success, it will dispatch LOGIN_SUCCESS otherwise it will dispatch LOGIN_FAIL to the Store.

Then all we need to do for the code above to run is to put it inside the middleware:

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
//...
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducers, applyMiddleware(sagaMiddleware));
//...
sagaMiddleware.run(rootSaga);

That's it! The next step is to create a reducer to handle all the actions dispatched, which is not covered in here.

The way a saga with generator function works is that once the promise is run and yielded, it will not continue until the promise is resolved. Everytime an effect or a yield statement is found, it will pause or suspend and resume again after the promise is resolved.