Redux Strategy with Async Data and Interaction
Aug 27, 2020 • 6 Minute Read
Introduction
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.
Setting Up App
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
npx create-react-app react-thunk-app
Install Redux and react-redux, the core packages for setting up Redux in your React app
npm i --save redux react-redux
Install redux-thunk to use thunk
npm i --save redux-thunk
Creating the Store
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.
import {createStore,applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
const initState={
user:{}
}
const rootReducer=(state=initState,action)=>{
switch(action.type){
case "ADD_USER":
return{
...state,
user: action.payload
}
default:
return state;
}
}
export 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.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {Provider} from 'react-redux';
import store from './store';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
Creating Actions
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().
import {getRandomUser} from './service';
const ADD_USER="ADD_USER";
export const getRandomUserAction=()=>async(dispatch,getState)=>{
const newUser= await getRandomUser()
dispatch({
type:ADD_USER,
payload:newUser
})
}
Creating Service
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.
const users=['James','Michael','Harry','Sam','Dubby']
export const getRandomUser=()=>{
return new Promise(resolve=>setTimeout(()=>{
resolve(users[Math.floor(Math.random()*10)%users.length])
},3000))
}
Consuming State
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.
import React,{useEffect} from 'react';
import {connect} from 'react-redux';
import {getRandomUserAction} from './actions';
import './App.css';
function App({getUser,user}) {
useEffect(()=>{
console.log(user)
},[user])
return (
<div className="App">
<button onClick={getUser}>Get a random user</button>
{
Object.values(user).length>0 ? user : <>No random user!</>
}
</div>
);
}
const mapStateToProps=(state)=>({user:state.user})
const mapDispatchToProps={
getUser:getRandomUserAction
}
export default connect(mapStateToProps,mapDispatchToProps)(App);
Conclusion
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.