How to Use createAsyncThunk in React with Redux Toolkit
Use
createAsyncThunk from Redux Toolkit to define async actions in React that handle pending, fulfilled, and rejected states automatically. Dispatch the thunk like a normal action and handle loading or errors in your component state.Syntax
createAsyncThunk takes two arguments: a string action type and a payload creator function that returns a promise. It generates async action creators and action types for pending, fulfilled, and rejected states.
The syntax looks like this:
typePrefix: a string to identify the action type.payloadCreator: an async function that returns the data or throws an error.
javascript
import { createAsyncThunk } from '@reduxjs/toolkit'; const fetchData = createAsyncThunk( 'data/fetchData', // action type prefix async (arg, thunkAPI) => { const response = await fetch('https://api.example.com/data'); if (!response.ok) { throw new Error('Network response was not ok'); } return await response.json(); } );
Example
This example shows how to create an async thunk to fetch user data, handle loading and error states, and display the data in a React component using Redux Toolkit.
javascript
import React from 'react'; import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { Provider, useDispatch, useSelector } from 'react-redux'; // Async thunk to fetch users const fetchUsers = createAsyncThunk('users/fetchUsers', async () => { const response = await fetch('https://jsonplaceholder.typicode.com/users'); if (!response.ok) throw new Error('Failed to fetch users'); return await response.json(); }); // Slice with extraReducers to handle thunk states const usersSlice = createSlice({ name: 'users', initialState: { users: [], loading: false, error: null }, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchUsers.pending, (state) => { state.loading = true; state.error = null; }) .addCase(fetchUsers.fulfilled, (state, action) => { state.loading = false; state.users = action.payload; }) .addCase(fetchUsers.rejected, (state, action) => { state.loading = false; state.error = action.error.message; }); }, }); const store = configureStore({ reducer: { users: usersSlice.reducer } }); function UsersList() { const dispatch = useDispatch(); const { users, loading, error } = useSelector((state) => state.users); React.useEffect(() => { dispatch(fetchUsers()); }, [dispatch]); if (loading) return <p>Loading users...</p>; if (error) return <p>Error: {error}</p>; return ( <ul> {users.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> ); } export default function App() { return ( <Provider store={store}> <h1>User List</h1> <UsersList /> </Provider> ); }
Output
User List
- Leanne Graham
- Ervin Howell
- Clementine Bauch
- Patricia Lebsack
- Chelsey Dietrich
- Mrs. Dennis Schulist
- Kurtis Weissnat
- Nicholas Runolfsdottir V
- Glenna Reichert
- Clementina DuBuque
Common Pitfalls
- Not handling the
pending,fulfilled, andrejectedstates in your slice causes UI to not update properly. - Forgetting to return the data from the async function will result in
undefinedpayload. - Not catching errors or throwing inside the async function prevents
rejectedaction from firing. - Dispatching the thunk incorrectly (not as a function) will cause errors.
javascript
/* Wrong: Not returning data and no error handling */ const fetchDataWrong = createAsyncThunk('data/fetch', async () => { await fetch('https://api.example.com/data'); // forgot return }); /* Right: Return awaited data and throw error if needed */ const fetchDataRight = createAsyncThunk('data/fetch', async () => { const response = await fetch('https://api.example.com/data'); if (!response.ok) throw new Error('Fetch failed'); return await response.json(); });
Quick Reference
createAsyncThunk Cheat Sheet:
createAsyncThunk(typePrefix, asyncFunction): creates async action.- Dispatch thunk like
dispatch(fetchData()). - Handle states in slice with
extraReducersusingbuilder.addCase. - States:
pending(loading),fulfilled(success),rejected(error). - Use
thunkAPIargument for extra control (e.g.,rejectWithValue).
Key Takeaways
Use createAsyncThunk to simplify async logic and automatically generate action types for loading, success, and error states.
Always handle pending, fulfilled, and rejected cases in your slice's extraReducers to update UI state correctly.
Return data or throw errors inside the async function to trigger the correct action states.
Dispatch the thunk like a normal action creator function inside your React components.
Use thunkAPI for advanced control like rejecting with custom values or accessing state.