Middleware And Async Action In Redux

Introduction

 
In the previous article, we have learned about the multiple-reducer concept in Redux along with its implementation using the 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 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 can be composed 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, implement it by installing it in Visual Studio Code.
  1. npm i --save redux-logger  
Middleware And Async Action In Redux
 
Now, we will be updating the previous code as below.
 
First, import the redux-logger after installation.
  1. const reduxLogger = require('redux-logger')   
Now, create a variable for applyMiddleware function from the Redux library.
  1. const applyMiddleware = redux.applyMiddleware  
  2.   
  3. const logger = reduxLogger.createLogger()   
After that, update it in createStore as the second argument.
  1. const store = createStore(rootReducer,applyMiddleware(logger))   
The overall code looks like below.
  1. const redux = require('redux')  
  2. const reduxLogger = require('redux-logger')  
  3. console.log("Index js in redux app")  
  4.   
  5. const createStore = redux.createStore  
  6. const combineReducer = redux.combineReducers  
  7. const applyMiddleware = redux.applyMiddleware  
  8. const logger = reduxLogger.createLogger()  
  9.   
  10. const LOGIN = 'LOGIN'  
  11. const USER_DETAIL = 'USER_DETAIL'  
  12. // action  
  13. function loggedIn(user, pwd) {  
  14.     return {  
  15.         type: LOGIN,  
  16.         username: user,  
  17.         password: pwd,  
  18.         loggedInStatus: ""  
  19.     }  
  20. }  
  21. function updateUserName(FirstName, LastName, UserName) {  
  22.     return {  
  23.         type: USER_DETAIL,  
  24.         FirstName: FirstName,  
  25.         LastName: LastName,  
  26.         UserName: UserName  
  27.     }  
  28. }  
  29.   
  30. function callLoginApi(username, password) {  
  31.     if (username === 'admin' && password === 'admin') {  
  32.         return "Login Success";  
  33.     } else {  
  34.         return 'Invalid email and password';  
  35.     }  
  36. }  
  37.   
  38. const initialLoginState = {  
  39.     username: "test",  
  40.     password: "test",  
  41.     loggedInStatus: ""  
  42. }  
  43.   
  44. const initialUserState = {  
  45.     FirstName: "",  
  46.     LastName: "",  
  47.     UserName: ""  
  48. }  
  49.   
  50. const loginReducer = (state = initialLoginState, action) => {  
  51.     switch (action.type) {  
  52.         case LOGIN:  
  53.             return {  
  54.                 ...state,  
  55.                 username: action.username,  
  56.                 password: action.password,  
  57.                 loggedInStatus: callLoginApi(action.username, action.password)  
  58.             }  
  59.         default:  
  60.             return state  
  61.     }  
  62. }  
  63.   
  64. const UserReducer = (state = initialUserState, action) => {  
  65.     switch (action.type) {         
  66.         case USER_DETAIL:  
  67.             return {  
  68.                 ...state,  
  69.                 FirstName: action.FirstName,  
  70.                 LastName: action.LastName,  
  71.                 UserName: action.UserName  
  72.             }  
  73.         default:  
  74.             return state  
  75.     }  
  76. }  
  77.   
  78. const rootReducer = combineReducer({  
  79.     login : loginReducer,  
  80.     userDetail : UserReducer  
  81. })  
  82. const store = createStore(rootReducer,applyMiddleware(logger))  
  83. console.log("Initial State", store.getState())  
  84. const unsubscribe = store.subscribe(() => {})  
  85. store.dispatch(loggedIn("user""user"))  
  86. store.dispatch(loggedIn("admin""admin"))  
  87. store.dispatch(updateUserName("priyanka""jain""[email protected]"))  
  88. store.dispatch(updateUserName("test""test""[email protected]"))  
  89. unsubscribe()   
This will display the output as below.
 
Middleware And Async Action In Redux
 
Middleware And Async Action In Redux
 
In the image, the output is displaying the log, stating which action is performing after the % sign.
 
Now, we are going to learn about Asynchronous action along with middleware.
 

Async Actions

 
In the previous article, we have seen about synchronous action, i.e., as soon as the action is dispatched, the state gets updated. But there are some scenarios when we need to update the state based on some API calls or something that takes time to execute. Then, we need to use Async Actions.
 
Let’s look at the demo which fetches data from API using the Redux application.
 
Now, for fetching data from API, we will see how to write code in the Redux application.
 
First of all, our state should go like this:
  1. State = {  
  2.       loading : true// display till data load  
  3.       data : [], // once data loaded it will be displayed in it  
  4.       error:’’ // While calling API if any error occurs here it will be stored  
  5. }  
Secondly, these 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.
  1. CASE FETCH_DATA_REQUEST:  
  2.             loading: true  
  3. CASE FETCH_DATA_SUCCESS:  
  4.             loading: false,  
  5.             users:data, // data returned from API  
  6.   
  7. CASE FETCH_DATA_FAILURE:  
  8.             loading : false,  
  9.             error : error // if any error occurred by API  
Now, let’s create a new JavaScript file as - asyncActionDemo.js.
  1. const redux = require('redux')  
  2. const createStore = redux.createStore  
  3.   
  4. const initialState = {  
  5.     loading: false,  
  6.     data: [],  
  7.     error: ''  
  8. }  
  9.   
  10. const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST'  
  11. const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS'  
  12. const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE'  
  13.   
  14. const fetchDataRequest = () => {  
  15.     return {  
  16.         type: FETCH_DATA_REQUEST  
  17.     }  
  18. }  
  19.   
  20. const fetchDataSuccess = data => {  
  21.     return {  
  22.         type: FETCH_DATA_SUCCESS,  
  23.         payload: data  
  24.     }  
  25. }  
  26.   
  27. const fetchDataError = error => {  
  28.     return {  
  29.         type: FETCH_DATA_FAILURE,  
  30.         payload: error  
  31.     }  
  32. }  
  33.   
  34. const reducer = (state = initialState, action) => {  
  35.     switch (action.type) {  
  36.         case FETCH_DATA_REQUEST:  
  37.             return {  
  38.                 ...state,  
  39.                 loading: true  
  40.             }  
  41.         case FETCH_DATA_SUCCESS:  
  42.             return {  
  43.                 loading: false,  
  44.                 data: action.payload,  
  45.                 error:''  
  46.             }  
  47.         case FETCH_DATA_FAILURE:  
  48.             return {  
  49.                 loading: false,  
  50.                 users:[],  
  51.                 error: action.payload  
  52.             }  
  53.     }  
  54. }  
  55.   
  56. const store = createStore(reducer)   
After the basic definition of State, Action, and Reducers, now we will need to add API calls and for that, we will need to install 2 more packages, as mentioned below.
 
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 that will be applied to the Redux store.
 
So first, we need to install both the packages in the terminal using the command.
  1. npm install axios redux-thunk  
Middleware And Async Action In Redux
 
After the successful installation of packages, we will import it in the JS file.
  1. const applyMiddleware = redux.applyMiddleware  
  2. const thunkMiddleware = require('redux-thunk').default  
  3. const axios = require('axios')   
and update the middleware in the store.
  1. const store = createStore(reducer,applyMiddleware(thunkMiddleware))   
Now, we will be creating a method that will call the API, but as we know, in an Action creator, only actions are returned; using thunk middleware, we have the functionality of returning a function that is not required to be pure.
 
In this demo, we will be using dummy API “https://jsonplaceholder.typicode.com”.
 
We will add an action creator containing the API call.
  1. const fetchData = () => {  
  2.     return function (dispatch) {  
  3.         dispatch(fetchDataRequest())  
  4.         axios.get("https://jsonplaceholder.typicode.com/users")  
  5.             .then(response => {  
  6.                 const data = response.data.map(user => user.name)  
  7.                 dispatch(fetchDataSuccess(data))  
  8.             })  
  9.             .catch(error => {  
  10.                 dispatch(fetchDataError(error.message))  
  11.             })  
  12.     }  
  13. }   
And then, dispatch it after subscribing the store.
  1. store.subscribe(() => { console.log(store.getState()) })  
  2. store.dispatch(fetchData())   
Now, the complete code goes like below.
  1. const redux = require('redux')  
  2. const createStore = redux.createStore  
  3. const applyMiddleware = redux.applyMiddleware  
  4. const thunkMiddleware = require('redux-thunk').default  
  5. const axios = require('axios')  
  6.   
  7. const initialState = {  
  8.     loading: false,  
  9.     data: [],  
  10.     error: ''  
  11. }  
  12.   
  13. const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST'  
  14. const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS'  
  15. const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE'  
  16.   
  17. const fetchDataRequest = () => {  
  18.     return {  
  19.         type: FETCH_DATA_REQUEST  
  20.     }  
  21. }  
  22.   
  23. const fetchDataSuccess = data => {  
  24.     return {  
  25.         type: FETCH_DATA_SUCCESS,  
  26.         payload: data  
  27.     }  
  28. }  
  29.   
  30. const fetchDataError = error => {  
  31.     return {  
  32.         type: FETCH_DATA_FAILURE,  
  33.         payload: error  
  34.     }  
  35. }  
  36.   
  37. const reducer = (state = initialState, action) => {  
  38.     switch (action.type) {  
  39.         case FETCH_DATA_REQUEST:  
  40.             return {  
  41.                 ...state,  
  42.                 loading: true  
  43.             }  
  44.         case FETCH_DATA_SUCCESS:  
  45.             return {  
  46.                 loading: false,  
  47.                 data: action.payload,  
  48.                 error: ''  
  49.             }  
  50.         case FETCH_DATA_FAILURE:  
  51.             return {  
  52.                 loading: false,  
  53.                 users: [],  
  54.                 error: action.payload  
  55.             }  
  56.     }  
  57. }  
  58.   
  59. const fetchData = () => {  
  60.     return function (dispatch) {  
  61.         dispatch(fetchDataRequest())  
  62.         axios.get("https://jsonplaceholder.typicode.com/users")  
  63.             .then(response => {  
  64.                 const data = response.data.map(user => user.name)  
  65.                 dispatch(fetchDataSuccess(data))  
  66.             })  
  67.             .catch(error => {  
  68.                 dispatch(fetchDataError(error.message))  
  69.             })  
  70.     }  
  71. }  
  72. const store = createStore(reducer, applyMiddleware(thunkMiddleware))  
  73. store.subscribe(() => { console.log(store.getState()) })  
  74. store.dispatch(fetchData())  
The output will display as below.
 
Middleware And Async Action In Redux
 
Now, modify the URL as incorrect.
  1. const fetchData = () => {  
  2.     return function (dispatch) {  
  3.         dispatch(fetchDataRequest())  
  4.         axios.get("https://jsossnplaceholder.typicode.com/users")  
  5.             .then(response => {  
  6.                 const data = response.data.map(user => user.name)  
  7.                 dispatch(fetchDataSuccess(data))  
  8.             })  
  9.             .catch(error => {  
  10.                 dispatch(fetchDataError(error.message))  
  11.             })  
  12.     }  
  13. }   
The output will now be displayed as below.
 
Middleware And Async Action In Redux
 

Summary

 
In this article, we have learned the concept of Middleware and how it is implemented in the Redux application. We have also learned about Async action, why it is required, and how it can be implemented in Redux. You can download the attached source code along with this article.
 
With this article, I've finished the concept of Redux. Now, I will move on to the learning of Redux in ReactJS. If you have any confusion or suggestions, please do comment.