Published: June 16 2022

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

Path: /src/_store/users.slice.js

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

Path: /src/_store/index.js

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

Path: /src/home/Home.jsx

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

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by