Introduction to Middleware and Async Action in Redux

Introduction

In the previous article, we have learned about multiple reducer concept in Redux along with its implementation using combineReducer() method. Now in this article, we will be learning about the concept of Middleware along with its usage and Async actions in Redux.

Middleware

In Redux, Middleware provides a third-party extension point between dispatching an action and handling the action off to the reducer

Action <-> Middleware <-> Dispatcher

Middleware provides a way to extend Redux with custom functionality. It is mainly used for Logging, crash reporting, asynchronous requests, route handling and many more.

The best feature of middleware is that it’s composable in a chain. You can use multiple independent third-party middlewares in a single project.

Let’s start with the implementation of Middleware using redux-logger.

So, to implement it by installing it in visual studio code,

npm i --save redux-logger  

Now updating previous code as below,

First import redux-logger after installation,

const reduxLogger = require('redux-logger')   

Now create a variable for applyMiddleware function from the Redux library,

const applyMiddleware = redux.applyMiddleware  
  
const logger = reduxLogger.createLogger()   

After that updating it in createStore as the second argument,

const store = createStore(rootReducer,applyMiddleware(logger))   

Now the overall code looks like,

const redux = require('redux')  
const reduxLogger = require('redux-logger')  
console.log("Index js in redux app")  
  
const createStore = redux.createStore  
const combineReducer = redux.combineReducers  
const applyMiddleware = redux.applyMiddleware  
const logger = reduxLogger.createLogger()  
  
const LOGIN = 'LOGIN'  
const USER_DETAIL = 'USER_DETAIL'  
// action  
function loggedIn(user, pwd) {  
    return {  
        type: LOGIN,  
        username: user,  
        password: pwd,  
        loggedInStatus: ""  
    }  
}  
function updateUserName(FirstName, LastName, UserName) {  
    return {  
        type: USER_DETAIL,  
        FirstName: FirstName,  
        LastName: LastName,  
        UserName: UserName  
    }  
}  
  
function callLoginApi(username, password) {  
    if (username === 'admin' && password === 'admin') {  
        return "Login Success";  
    } else {  
        return 'Invalid email and password';  
    }  
}  
  
const initialLoginState = {  
    username: "test",  
    password: "test",  
    loggedInStatus: ""  
}  
  
const initialUserState = {  
    FirstName: "",  
    LastName: "",  
    UserName: ""  
}  
  
const loginReducer = (state = initialLoginState, action) => {  
    switch (action.type) {  
        case LOGIN:  
            return {  
                ...state,  
                username: action.username,  
                password: action.password,  
                loggedInStatus: callLoginApi(action.username, action.password)  
            }  
        default:  
            return state  
    }  
}  
  
const UserReducer = (state = initialUserState, action) => {  
    switch (action.type) {         
        case USER_DETAIL:  
            return {  
                ...state,  
                FirstName: action.FirstName,  
                LastName: action.LastName,  
                UserName: action.UserName  
            }  
        default:  
            return state  
    }  
}  
  
const rootReducer = combineReducer({  
    login : loginReducer,  
    userDetail : UserReducer  
})  
const store = createStore(rootReducer,applyMiddleware(logger))  
console.log("Initial State", store.getState())  
const unsubscribe = store.subscribe(() => {})  
store.dispatch(loggedIn("user", "user"))  
store.dispatch(loggedIn("admin", "admin"))  
store.dispatch(updateUserName("priyanka", "jain", "prynka.m.jain@gmail.com"))  
store.dispatch(updateUserName("test", "test", "test@gmail.com"))  
unsubscribe()  

This will display the output as below,

As in the image, output is displaying the log that which action is performing after % sign.

Now we are going to learn about Asynchronous action along with middleware.

Async Actions

As in the previous article, we have seen about synchronous action in which as soon as action is dispatched, the state gets updated. But there are some scenarios when we need to update state based on some API calls or something that takes time to execute than we need to use Async Actions.

Let’s look at the demo which fetches data from API using Redux application,

Now for fetching data from API we will see how to write code in Redux application,

First of all, our state should go like,

state = {  
      loading : true, // display till data load  
      data : [], // once data loaded it will be displayed in it  
      error:’’ // While calling API if any error occurs here it will be stored  
}  

Secondly, Actions need to be performed,

FETCH_DATA_REQUEST – Retrieve a list of data from API

FETCH_DATA_SUCCESS – When data retrieval is done successfully

FETCH_DATA_FAILURE – When there is an error while retrieving data

At last, Reducer will perform an evaluation based on actions,

CASE FETCH_DATA_REQUEST:  
            loading: true  
CASE FETCH_DATA_SUCCESS:  
            loading: false,  
            users:data, // data returned from API  
  
CASE FETCH_DATA_FAILURE:  
            loading : false,  
            error : error // if any error occurred by API  

Now let’s create a new JavaScript file asyncActionDemo.js,

const redux = require('redux')  
const createStore = redux.createStore  
  
const initialState = {  
    loading: false,  
    data: [],  
    error: ''  
}  
  
const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST'  
const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS'  
const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE'  
  
const fetchDataRequest = () => {  
    return {  
        type: FETCH_DATA_REQUEST  
    }  
}  
  
const fetchDataSuccess = data => {  
    return {  
        type: FETCH_DATA_SUCCESS,  
        payload: data  
    }  
}  
  
const fetchDataError = error => {  
    return {  
        type: FETCH_DATA_FAILURE,  
        payload: error  
    }  
}  
  
const reducer = (state = initialState, action) => {  
    switch (action.type) {  
        case FETCH_DATA_REQUEST:  
            return {  
                ...state,  
                loading: true  
            }  
        case FETCH_DATA_SUCCESS:  
            return {  
                loading: false,  
                data: action.payload,  
                error:''  
            }  
        case FETCH_DATA_FAILURE:  
            return {  
                loading: false,  
                users:[],  
                error: action.payload  
            }  
    }  
}  
  
const store = createStore(reducer)  

After the basic definition of State, Action, and Reducers now we will need to add API calls for that we will require to install 2 more packages likes

axios

To make a request to API calls

redux-thunk

It is a standard way to define Asynchronous action creators in Redux application. It is a middleware which will be applied to Redux store.

So first we need to install both the packages in terminal using the command

npm install axios redux-thunk  

After successful installation of packages,

We will import it in the js file,

const applyMiddleware = redux.applyMiddleware  
const thunkMiddleware = require('redux-thunk').default  
const axios = require('axios')  

and update the middleware in store,

const store = createStore(reducer,applyMiddleware(thunkMiddleware))   

Now we will be creating a method which will call API, but as we know in Action creator only actions are returned but using thunk middleware we have the functionality of returning function which is not required to be pure.

In this demo, we will be using dummy API “https://jsonplaceholder.typicode.com

We will add action creator containing API call,

const fetchData = () => {  
    return function (dispatch) {  
        dispatch(fetchDataRequest())  
        axios.get("https://jsonplaceholder.typicode.com/users")  
            .then(response => {  
                const data = response.data.map(user => user.name)  
                dispatch(fetchDataSuccess(data))  
            })  
            .catch(error => {  
                dispatch(fetchDataError(error.message))  
            })  
    }  
}   

And then dispatch it after subscribing the store,

store.subscribe(() => { console.log(store.getState()) })  
store.dispatch(fetchData())   

Now the complete code goes like below,

const redux = require('redux')  
const createStore = redux.createStore  
const applyMiddleware = redux.applyMiddleware  
const thunkMiddleware = require('redux-thunk').default  
const axios = require('axios')  
  
const initialState = {  
    loading: false,  
    data: [],  
    error: ''  
}  
  
const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST'  
const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS'  
const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE'  
  
const fetchDataRequest = () => {  
    return {  
        type: FETCH_DATA_REQUEST  
    }  
}  
  
const fetchDataSuccess = data => {  
    return {  
        type: FETCH_DATA_SUCCESS,  
        payload: data  
    }  
}  
  
const fetchDataError = error => {  
    return {  
        type: FETCH_DATA_FAILURE,  
        payload: error  
    }  
}  
  
const reducer = (state = initialState, action) => {  
    switch (action.type) {  
        case FETCH_DATA_REQUEST:  
            return {  
                ...state,  
                loading: true  
            }  
        case FETCH_DATA_SUCCESS:  
            return {  
                loading: false,  
                data: action.payload,  
                error: ''  
            }  
        case FETCH_DATA_FAILURE:  
            return {  
                loading: false,  
                users: [],  
                error: action.payload  
            }  
    }  
}  
  
const fetchData = () => {  
    return function (dispatch) {  
        dispatch(fetchDataRequest())  
        axios.get("https://jsonplaceholder.typicode.com/users")  
            .then(response => {  
                const data = response.data.map(user => user.name)  
                dispatch(fetchDataSuccess(data))  
            })  
            .catch(error => {  
                dispatch(fetchDataError(error.message))  
            })  
    }  
}  
const store = createStore(reducer, applyMiddleware(thunkMiddleware))  
store.subscribe(() => { console.log(store.getState()) })  
store.dispatch(fetchData())  

The output it will display as below,

Now modify the url as incorrect,

const fetchData = () => {  
    return function (dispatch) {  
        dispatch(fetchDataRequest())  
        axios.get("https://jsossnplaceholder.typicode.com/users")  
            .then(response => {  
                const data = response.data.map(user => user.name)  
                dispatch(fetchDataSuccess(data))  
            })  
            .catch(error => {  
                dispatch(fetchDataError(error.message))  
            })  
    }  
}   

The output will now be displayed as,

Summary

In this article, we have learned about the concept of Middleware and how it is implemented in Redux application. We have also learned about Async action, why it is required and how it can be implemented in Redux.

With this article, I’ve finished the concept of Redux. Now, I will move on to the learning of Redux in ReactJS.

Any question or feedback or suggestion, please do comment and let me know.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s