React Redux apps are common on the web today due to their versatility, potential to scale, and innumerable features. Using actions
and reducers
complements your app's architecture and allows you to keep your code clean while implementing complex features. However, when you need to use asynchronous operations, such as dispatching actions after receiving the response from a server, actions
and reducers
alone aren't sufficient. This guide demonstrates how to interact with asynchronous data in your React Redux app using a Redux library called redux-thunk.
In this example, you'll develop a simple app that returns a random username as a string from a given array asynchronously. To achieve this you’ll use a middleware called thunk
that lets you intercept your actions
before it reaches the reducer
.
Create a blank React project by running
1npx create-react-app react-thunk-app
Install Redux and react-redux, the core packages for setting up Redux in your React app
1npm i --save redux react-redux
Install redux-thunk to use thunk
1npm i --save redux-thunk
Import createStore
to create a global state for your components along with applyMiddleware
to use a middleware in your store. Next import thunk
from redux-thunk. Create a reducer rootReducer
that takes the state
and action
and returns the updated state
. Pass rootReducer
and applyMiddleware
functions as parameters to createStore
. Finally, to use thunk
and simply pass it to applyMiddleware
as a parameter. Most of the code inside store.js
is self-explanatory and you can see how easy it is so use thunk
in your Redux store.
1import {createStore,applyMiddleware} from 'redux';
2import thunk from 'redux-thunk';
3
4const initState={
5 user:{}
6}
7
8const rootReducer=(state=initState,action)=>{
9 switch(action.type){
10 case "ADD_USER":
11 return{
12 ...state,
13 user: action.payload
14 }
15 default:
16 return state;
17
18 }
19}
20
21export default createStore(rootReducer,applyMiddleware(thunk))
Inside index.js
import Provider
from Redux and pass your store
as props
to this provider component. You also need to wrap your entire app inside this provider component.
1import React from 'react';
2import ReactDOM from 'react-dom';
3import './index.css';
4import App from './App';
5import * as serviceWorker from './serviceWorker';
6import {Provider} from 'react-redux';
7import store from './store';
8
9ReactDOM.render(
10 <React.StrictMode>
11 <Provider store={store}>
12 <App />
13 </Provider>
14 </React.StrictMode>,
15 document.getElementById('root')
16);
It's a good practice to perform dispatching of your actions inside an action creator. Consider the following code inside the actions.js
file that takes in the dispatch
and getState
as a parameter and dispatches an action
of type ADD_USER
. It returns the newUser
inside the payload
property and fetches this newUser
from a service called getRandomUse()
.
1import {getRandomUser} from './service';
2
3const ADD_USER="ADD_USER";
4
5export const getRandomUserAction=()=>async(dispatch,getState)=>{
6 const newUser= await getRandomUser()
7 dispatch({
8 type:ADD_USER,
9 payload:newUser
10 })
11}
You can imagine getRandomUser()
as a function that returns a random user from a backend service. For brevity purposes, the below code mimics a backend service and returns a promise wrapped around a random user from a list of users after three seconds. The delay is purposely added to replicate the asynchronous nature of an API response. In a practical scenario, you'll make an API call to your server inside this file to get some data.
1const users=['James','Michael','Harry','Sam','Dubby']
2
3export const getRandomUser=()=>{
4 return new Promise(resolve=>setTimeout(()=>{
5 resolve(users[Math.floor(Math.random()*10)%users.length])
6 },3000))
7}
Inside main App.js
, import connect
from react-redux and getRandomUser
from your actions.js
. Create a function mapStateToProps
, which pulls the required state from your store so that it can be passed down as props
to your component. Create an object mapDispatchToProps
that maps your dispatch
to a simple function that can be passed down as props
to your component. Call the connect()
function and pass mapStateToProps
and mapDispatchToProps
as parameters. Finally, wrap your app component inside the connect()
function. Now your store and actions are ready to be used by your component.
To see this in action create a simple UI button that fires the getUser()
function and prints the random user from the state.
1import React,{useEffect} from 'react';
2import {connect} from 'react-redux';
3import {getRandomUserAction} from './actions';
4
5import './App.css';
6
7function App({getUser,user}) {
8 useEffect(()=>{
9 console.log(user)
10 },[user])
11 return (
12 <div className="App">
13 <button onClick={getUser}>Get a random user</button>
14 {
15 Object.values(user).length>0 ? user : <>No random user!</>
16 }
17 </div>
18 );
19}
20
21const mapStateToProps=(state)=>({user:state.user})
22
23const mapDispatchToProps={
24 getUser:getRandomUserAction
25}
26
27export default connect(mapStateToProps,mapDispatchToProps)(App);
Using thunk
you can communicate with your actions right before they reach your reducer. thunk
has a number of use cases built around practical web apps that need to dispatch different actions depending on the response from the server. The example used in this guide can be used as a boilerplate for your React Redux apps where you need to wait for some response from the server before modifying your state.