Redux Saga 101

Feb 15, 2020

I have been working with React and Redux for about 4 years but until now had never used redux-saga. I had a few cursory glances through the documentation but the weird generator syntax put me off. Besides, redux-thunk as an alternative seemed good enough for handling side effects such as API calls.

But alas, a new project I am working on uses redux-saga so it was finally time to learn it. This post is just a collection of my notes and is intended to serve as a reference mainly for myself.

If you’re new to redux-saga, you should probably start with the official docs.

What is redux-saga?

A redux middleware that aims to make application side effects like data fetching easier to manage and test. It’s an alternative to the popular redux-thunk library.

What is a saga?

A saga is like a separate thread in your application that’s solely responsible for side effects.

A saga is written as a generator function that yields plain JS objects known as Effects to the redux-saga middleware. The redux-saga middleware is responsible for dealing with the different Effects (e.g. by dispatching an action to the Redux store).

An example saga

// sagas.ts
import { put, takeEvery, all } from 'redux-saga/effects';

export const delay = ms => new Promise(res => setTimeout(res, ms));

function* helloSaga() {
  console.log('Hello Sagas!');

// Our worker Saga: will perform the async increment task
function* incrementAsync() {
  yield call(delay, 1000);
  yield put({ type: 'INCREMENT' });

// Our watcher Saga: spawn a new incrementAsync task on each INCREMENT_ASYNC
function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync);

// notice how we export the rootSaga single entry point to start all Sagas at once
export default function* rootSaga() {
  yield all([helloSaga(), watchIncrementAsync()]);
// store.ts
const sagaMiddleware = createSagaMiddleware();

const store = createStore(reducer, applyMiddleware(sagaMiddleware));

// Run the saga;

Testing sagas

One of the supposed advantages of using redux-saga is that it makes testing much easier, at least easier than redux-thunk.

Here’s an example of testing the incrementAsync function from above.

import { call, put } from 'redux-saga/effects';
import { incrementAsync, delay } from '../sagas';

describe('incrementAsync saga', () => {
  const iter = incrementAsync();

  test('calls delay(1000)', () => {
    expect(, 1000));

  test('dispatches INCREMENT action', () => {
    expect({ type: 'INCREMENT' }));

  test('should finish', () => {
    expect({ done: true, value: undefined });

API reference


Spawns new task every time an action is dispatched.


yield takeEvery('INCREMENT_ASYNC', incrementAsync);


Suspends the generator until an action is dispatched.


// Waits until ADD_TODO is dispatched
const action = yield take('ADD_TODO');


Dispatches an action to the store.


yield put({ type: 'LOGOUT' });


Calls an asynchronous function / generator and waits for it to resolve / return before continuing execution.


const token = yield call(Api.authorize, user, password);


Starts a task in the background, allowing the function to continue its execution without waiting for the forked task to complete..

Calling yield fork(someTask) returns a Task object which we can then make use of by cancelling it for example.


yield fork(authorize, user, password);
// Continue execution without waiting for `authorize` task to complete.


Cancels a task if it’s still running.


yield cancel(task);


Checks if a task has been cancelled.


function* mySaga() {
  try {
    // ...
  } finally {
    if (yield cancelled()) {
      // logic that should execute only on Cancellation
    // logic that should execute in all situations (e.g. closing a channel)


Runs multiple Effects in parallel and waits for all of them to complete.


function* mySaga() {
  const [customers, products] = yield all([


Uses a selector function to extract a slice of the store state.

Note: The selector function will run on the state after an action has been handled by the reducers.


// getPosts would be a selector
const posts = yield select(getPosts)