Hello Redux middlewares!

Sumit Kumar Rakshit
Webtips
Published in
5 min readJun 3, 2020
Redux-saga architecture
Photo by Nguyen Duc Hoang on youtube

Redux provides a powerful functionality of middlewares which makes your life easy in handling asynchronous calls when building a large scale web application.

A brief introduction on middlewares

Why need middlewares?

The action/reducer pattern is very clean for updating the state within an application. But what if we need to communicate with an external API? We need a way to run side effects without disrupting our action/reducer flow. Middleware allows for side effects to be run without blocking state updates.

How do middlewares work?

Someway between Action Creators and Reducer, the Middleware intercepts the action object before Reducer receives it and gives the functionality to perform additional actions with respect to the action dispatched.

Redux Middleware

Popular middlewares are redux-thunk, redux-saga. We will see both these middlewares in action.

Redux-thunk in action

Calling the function from your React component using this.props.actions.fetchProducts() gives a list of products from an api.

Here, the action creator fetchProducts does not simply return an object like a synchronous action creators.

fetchProducts returns a function, which gets executed by the Redux Thunk middleware. This function doesn’t need to be pure; it is thus allowed to have side effects, including executing asynchronous API calls. The function can also dispatch actions — like those synchronous actions (populateProducts).

Here is how your store would look like:

Changes in redux store

Redux-saga

redux-saga uses an ES6 feature called Generators to make those asynchronous flows easy to read, write, and test. Contrary to redux-thunk, you don’t end up in callback hell, you can test your asynchronous flows easily and your actions stay pure.

Why saga?

  • Easier to manage
  • More efficient to execute
  • Easy to test
  • Better at handling failures

Pre-requisites?

Before we see our saga in action, a few avengers you would want to explore:

Avenger 1: Generator functions

Generators are functions that can be paused and resumed, instead of executing all the statements of the function in one pass.

function* myGeneratorFunction() {
let a = yield "first value";
let b = yield "second value";
return "third value;"
}

use of function*, which is not a normal function, but a generator

When you invoke a generator function, it will return an iterator object. With each call of the iterator’s next() method, the generator’s body will be executed until the next yield statement and then pause:

const gen = myGeneratorFunction();
console.log(gen.next()); //{value: "first value", done: false}
console.log(gen.next()); //{value: "second value", done: false}
console.log(gen.next()); //{value: "third value;", done: true}
console.log(gen.next()); //{value: undefined, done: true}

This can make asynchronous code easy to write and understand. For example, instead of doing this:

fetch(URL).then((response) => {
console.log(response.json());
})

With generators, we could do this:

let response = yield fetch(url);
console.log(response.json());

In redux-saga, we write a saga whose job will be to watch for dispatched actions:

function* actionWatcher() {
}

Avenger 2: Basic helpers

It is very important to understand the basic helpers which are used to run the effects in saga. Let us visit a few of them:

  • takeEvery()
  • takeLatest()
  • put()
  • call()
  • all()
  • race()
  • takeEvery()
import { takeEvery } from 'redux-saga/effects'

function* actionWatcher() {
yield takeEvery('GET_PRODUCTS', fetchProducts)
}

The actionWatchers generator pauses until an GET_PRODUCTS action fires, and every time it fires, it’s going to call the fetchProducts function, infinitely, and concurrently (there is no need for fetchProducts to terminate its execution before a new once can run)

  • takeLatest()

Similar to takeEvery(), but only allows one function handler to run at a time, avoiding concurrency. If another action is fired when the handler is still running, it will cancel it, and run again with the latest data available.

  • put()

Dispatches an action to the Redux store. Instead of passing in the Redux store or the dispatch action to the saga, you can just use put()

yield put({ 
type: 'FETCH_PRODUCTS',
payload: res
})
  • call()

It is recommended to not use plain function call as these does not play nice with tests. So using call() allows you to wrap a function call and returns an object that can be easily inspected:

call(myFunction, "params");
  • all()
import { all } from 'redux-saga/effects'

const response = yield all([
actionWatcher1(),
actionWatcher2()
])

To execute all functions in parallel, wrap them into all(). all() won’t be resolved until both the functions return.

  • race()

It’s a race to see which one finishes first, and then we forget about the other participants.

All set now. Let us implement a redux-saga to make an api call in a React Redux architecture (the ‘Avengers Assemble’ moment?)

Redux-saga in action

  1. Setup store to use saga

Initialise Redux Saga by first importing it, create a middleware and apply saga as a middleware to the Redux Store:

import createSagaMiddleware from 'redux-saga';const sagaMiddleware = createSagaMiddleware()const store = createStore(
reducers,
applyMiddleware(sagaMiddleware)
)

Our last step is running the saga:

import rootSaga from './sagas'sagaMiddleware.run(rootSaga)

Now let us see how our store looks like by combining the above snippets:

2. Write saga in saga.js

function* actionWatcher() {
yield takeLatest('GET_PRODUCTS', fetchProducts)
}

Our generator function actionWatcher(Avenger 1?) watches for the action GET_PRODUCTS to be dispatched. Once GET_PRODUCTS is dispatched, fetchProducts callback is fired. ‘takeLatest’(refer to Avenger 2) ensures only the latest call is considered.

function* fetchProducts() {
const res = yield axios.get(URL).then((response) => response);
yield put({
type: 'FETCH_PRODUCTS',
payload: res
})
}

fetchProducts is responsible for making an API call and then dispatching an action FETCH_PRODUCTS using the ‘put’(refer to Avenger 2) helper.

export default function* rootSaga() {
yield all([
actionWatcher(),
]);
}

We, then, combine all our sagas using ‘all’(refer to Avenger 2) helper to make them available for the redux store.

Here is how our saga.js looks like:

3. Write action creator

Now instead of calling this.props.actions.fetchProducts() directly (like we did in redux-thunk), we will call the function this.props.actions.getProducts() from our React component and saga will call fetchProducts which will call the api and give us a list of products.

There you go… We just used redux-saga in our application.

Conclusion

This article has taught you:

  • Basics of middleware
  • Redux-thunk
  • Redux-saga
  • Concepts of generator functions

and is inspired by Sources:
https://github.com/reduxjs/redux-thunk
https://redux-saga.js.org/docs/basics/

Ready to see our redux-saga in action:

Watch demo: https://sraksh.github.io/shoppingCart-redux/
Github repo: https://github.com/sraksh/shoppingCart-redux/
Note: Switch to develop branch to see redux-thunk in action.

May your life be filled with sagas. Cheers! 😃

--

--