React + Redux Toolkit - Fetch Data in Async Action with createAsyncThunk
Tutorial built with React 18.1.0, Redux 4.2.0 and Redux Toolkit 1.8.2
This is a quick example of how to fetch data from an API in Redux using an async action created with the Redux Toolkit's createAsyncThunk()
function.
The below code snippets are from a React + Redux JWT authentication tutorial I posted recently that includes a live demo, so to see the code running check out React 18 + Redux - JWT Authentication Example & Tutorial.
Redux Users Slice
The users slice manages Redux state, actions and reducers for users in the React app. Each part of the slice is organised into its own function that is called from the top of the file to make it easier to see what's going on. initialState
defines the state properties in this slice with their initial values. The users
property is used to store all users fetched from the API. It defaults to an empty object and can hold one the following values:
{}
- initial state.{ loading: true }
- users are currently being fetched from the API.[{ ... }, { ... }, { ... }]
- array of users returned by the API.{ error: { message: 'an error message' } }
- request to the API failed and an error was returned.
Async Actions with createAsyncThunk()
The extraActions
object contains logic for asynchronous actions (things you have to wait for) such as API requests. Async actions are created with the Redux Toolkit createAsyncThunk()
function. The first parameter to createAsyncThunk is the name of the action, the standard convention for Redux action names is '[slice name]/[action name]'
e.g. ('users/getAll'
). The second parameter is the async function that performs the action and returns the result when it's finished.
For each async action created with createAsyncThunk()
, three Redux actions are automatically generated by the Redux Toolkit, one for each stage of the async action: pending
, fulfilled
, rejected
.
The extraReducers
object contains methods for updating Redux state at each of the three different stages of async actions generated by createAsyncThunk()
, and the object passed as a parameter to the createSlice()
function to include the extra reducers in the Redux slice.
The getAll()
action method fetches the users from the API, on success (fulfilled
) the returned users array (action.payload
) is stored in the Redux state users
property. The getAll action is dispatched to the Redux store and the users are rendered in the Home component below.
Export Actions and Reducer for Redux Slice
The userActions
export includes all sync actions (slice.actions
) and async actions (extraActions
) for the users slice.
The reducer for the users slice is exported as usersReducer
, which is used in the root Redux store to configure global state for the React app.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { fetchWrapper } from '_helpers';
// create slice
const name = 'users';
const initialState = createInitialState();
const extraActions = createExtraActions();
const extraReducers = createExtraReducers();
const slice = createSlice({ name, initialState, extraReducers });
// exports
export const userActions = { ...slice.actions, ...extraActions };
export const usersReducer = slice.reducer;
// implementation
function createInitialState() {
return {
users: {}
}
}
function createExtraActions() {
const baseUrl = `${process.env.REACT_APP_API_URL}/users`;
return {
getAll: getAll()
};
function getAll() {
return createAsyncThunk(
`${name}/getAll`,
async () => await fetchWrapper.get(baseUrl)
);
}
}
function createExtraReducers() {
return {
...getAll()
};
function getAll() {
var { pending, fulfilled, rejected } = extraActions.getAll;
return {
[pending]: (state) => {
state.users = { loading: true };
},
[fulfilled]: (state, action) => {
state.users = action.payload;
},
[rejected]: (state, action) => {
state.users = { error: action.error };
}
};
}
}
Redux Store
The store index file configures the root Redux store for the React application with the configureStore()
function. The returned Redux store contains the state properties auth
and users
which map to their corresponding slices.
The index file also re-exports all of the modules from the Redux slices in the folder. This enables Redux modules to be imported directly from the _store
folder without the path to the slice file. It also enables multiple imports from different files at once (e.g. import { store, authActions } from '_store';
)
import { configureStore } from '@reduxjs/toolkit';
import { authReducer } from './auth.slice';
import { usersReducer } from './users.slice';
export * from './auth.slice';
export * from './users.slice';
export const store = configureStore({
reducer: {
auth: authReducer,
users: usersReducer
},
});
React Home Component
The home page is displayed after signing in to the application, it shows the signed in user's name plus a list of all users in the tutorial application. The users are loaded into Redux state by calling dispatch(userActions.getAll());
from the useEffect()
hook function, the users are fetched and the Redux state is updated in users slice.
The users list is displayed if the users
state property contains an array with at least 1 item, which is confirmed by checking {users.length && ...}
.
A loading spinner is displayed while the API request for users is in progress/loading, and an error message is displayed if the request fails.
Redux state values are retrieved for auth
and users
data with the help of the useSelector()
hook function.
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { userActions } from '_store';
export { Home };
function Home() {
const dispatch = useDispatch();
const { user: authUser } = useSelector(x => x.auth);
const { users } = useSelector(x => x.users);
useEffect(() => {
dispatch(userActions.getAll());
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div>
<h1>Hi {authUser?.firstName}!</h1>
<p>You're logged in with React 18 + Redux & JWT!!</p>
<h3>Users from secure api end point:</h3>
{users.length &&
<ul>
{users.map(user =>
<li key={user.id}>{user.firstName} {user.lastName}</li>
)}
</ul>
}
{users.loading && <div className="spinner-border spinner-border-sm"></div>}
{users.error && <div className="text-danger">Error loading users: {users.error.message}</div>}
</div>
);
}
Need Some React Help?
Search fiverr for freelance React developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!