Skip to main content

Async Logic with `createAsyncThunk` (Part 1): Handling API Calls the Redux Way #116

📖 Introduction

Following our exploration of configureStore and createSlice, this article dives into one of the most powerful features of Redux Toolkit: createAsyncThunk. We will learn how to use this function to handle asynchronous logic, such as fetching data from an API, in a clean and standardized way.


📚 Prerequisites

Before we begin, please ensure you have a solid grasp of the following concepts:

  • createSlice and configureStore.
  • Asynchronous JavaScript (async/await).
  • The Fetch API.

🎯 Article Outline: What You'll Master

In this article, you will learn:

  • The "Why" of createAsyncThunk: Understanding the need for a standardized way to handle asynchronous logic in Redux.
  • What createAsyncThunk Is: How it works and what it does under the hood.
  • Core Implementation: How to use createAsyncThunk to fetch data from an API.

🧠 Section 1: The Core Concepts of createAsyncThunk

In a Redux application, you'll often need to perform asynchronous operations, such as fetching data from an API. Before Redux Toolkit, this required using middleware like redux-thunk and writing a lot of boilerplate code to dispatch actions for the different stages of the request (e.g., "pending", "fulfilled", "rejected").

createAsyncThunk is a function that simplifies this process. It accepts an action type string and a "payload creator" callback function that should return a promise. It then automatically dispatches promise lifecycle actions (pending, fulfilled, rejected) based on the result of the promise.

This allows you to write your asynchronous logic in a single place and have the different stages of the request handled automatically by your reducers.


💻 Section 2: Deep Dive - Implementation and Walkthrough

Let's see how to use createAsyncThunk to fetch a list of users from an API.

2.1 - Creating the Async Thunk

First, let's create an async thunk in our usersSlice.js file.

// features/users/usersSlice.js
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

export const fetchUsers = createAsyncThunk(
'users/fetchUsers',
async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
return data;
}
);

// ...

Step-by-Step Code Breakdown:

  1. createAsyncThunk: We call createAsyncThunk with two arguments:
    • The action type string: 'users/fetchUsers'.
    • The payload creator function: an async function that fetches the users and returns the data.
  2. fetchUsers: The fetchUsers variable is now an async thunk action creator that we can dispatch from our components.

2.2 - Handling the Lifecycle Actions in the Slice

Now, we need to handle the pending, fulfilled, and rejected actions in our slice. We do this using the extraReducers field in createSlice.

// features/users/usersSlice.js
// ...

export const usersSlice = createSlice({
name: 'users',
initialState: {
entities: [],
loading: 'idle',
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state, action) => {
state.loading = 'loading';
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = 'succeeded';
state.entities = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = 'failed';
});
},
});

export default usersSlice.reducer;

Step-by-Step Code Breakdown:

  1. extraReducers: We use the extraReducers field to handle actions that were not defined in the reducers field.
  2. builder.addCase: We use the builder.addCase method to handle each of the lifecycle actions.
    • fetchUsers.pending: When the request is pending, we set the loading state to 'loading'.
    • fetchUsers.fulfilled: When the request is successful, we set the loading state to 'succeeded' and update the entities array with the fetched data.
    • fetchUsers.rejected: When the request fails, we set the loading state to 'failed'.

💡 Conclusion & Key Takeaways

In this article, we've learned about createAsyncThunk and how it simplifies asynchronous logic in Redux Toolkit. We've seen how to create an async thunk to fetch data from an API and how to handle the different stages of the request in our slice.

Let's summarize the key takeaways:

  • createAsyncThunk is the standard way to handle asynchronous logic in Redux Toolkit.
  • It automatically dispatches pending, fulfilled, and rejected actions based on the result of a promise.
  • You can handle these actions in your slice using the extraReducers field.

Challenge Yourself: To solidify your understanding, try to add error handling to the fetchUsers thunk and display an error message in your component if the request fails.


➡️ Next Steps

You now have a basic understanding of createAsyncThunk. In the next article, "Async Logic with createAsyncThunk (Part 2)", we will build a more practical example of how to use this function in a React application.

Thank you for your dedication. Stay curious, and happy coding!


glossary

  • createAsyncThunk: A function from Redux Toolkit that simplifies asynchronous logic.
  • Thunk: A function that is returned from another function. In Redux, a thunk is a function that can have side effects.
  • Payload Creator: A function that returns a promise and is used to create the payload for the fulfilled action.

Further Reading