Home
Daily
📦 Redux Toolkit: Using `createAsyncThunk`
November 06, 2025
1 min

Table Of Contents

01
🧠 Why Do We Need Async Handling?
02
⚡ createAsyncThunk — The Right Way to Handle Async
03
🧱 Step 1 — Create the Async Thunk
04
🏪 Step 2 — Add This Slice to the Store
05
🎨 Step 3 — Display Loading, Error, & Data in UI
06
🧭 Recap

You’ve learned how to manage local and global state, create slices, and use useSelector and useDispatch. Now it’s time to take a big step forward: fetching real data from an API.

In this lesson, we’ll learn how to handle asynchronous operations (like API requests) using createAsyncThunk — a powerful helper provided by Redux Toolkit.


🧠 Why Do We Need Async Handling?

Most real-world applications need to load data from somewhere — for example:

  • Fetching a list of products
  • Loading user profile details
  • Searching movies or Pokémon

When we fetch data, we don’t get it instantly. The request could:

  • Take time (loading)
  • Return success (data received)
  • Fail (error returned)

So we need to handle three states in our UI:

  1. Loading
  2. Success
  3. Error

Redux Toolkit makes this simple.


createAsyncThunk — The Right Way to Handle Async

createAsyncThunk lets us:

  • Write async functions that return data
  • Automatically handle loading, fulfilled, and error states
  • Avoid writing extra boilerplate code

🧱 Step 1 — Create the Async Thunk

Create a new slice file, for example:

src/features/pokemon/pokemonSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
export const fetchPokemon = createAsyncThunk(
"pokemon/fetchPokemon",
async () => {
const res = await fetch("https://pokeapi.co/api/v2/pokemon?limit=20");
const data = await res.json();
return data.results;
}
);
const pokemonSlice = createSlice({
name: "pokemon",
initialState: {
items: [],
status: "idle", // idle | loading | succeeded | failed
error: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchPokemon.pending, (state) => {
state.status = "loading";
})
.addCase(fetchPokemon.fulfilled, (state, action) => {
state.status = "succeeded";
state.items = action.payload;
})
.addCase(fetchPokemon.rejected, (state) => {
state.status = "failed";
state.error = "Failed to fetch Pokémon.";
});
},
});
export default pokemonSlice.reducer;

What’s Happening Here?

PartMeaning
fetchPokemonThe async function that fetches data
.pendingAutomatically triggered when the request starts
.fulfilledTriggered when data is successfully received
.rejectedTriggered if the request fails

🏪 Step 2 — Add This Slice to the Store

Open your store.js:

import { configureStore } from "@reduxjs/toolkit";
import pokemonReducer from "./features/pokemon/pokemonSlice";
export const store = configureStore({
reducer: {
pokemon: pokemonReducer,
},
});

🎨 Step 3 — Display Loading, Error, & Data in UI

Open a UI component like App.jsx:

import { useSelector, useDispatch } from "react-redux";
import { fetchPokemon } from "./features/pokemon/pokemonSlice";
import { useEffect } from "react";
function App() {
const dispatch = useDispatch();
const { items, status, error } = useSelector((state) => state.pokemon);
useEffect(() => {
dispatch(fetchPokemon());
}, [dispatch]);
return (
<div style={{ padding: 20 }}>
<h1>Pokémon List</h1>
{status === "loading" && <p>Loading...</p>}
{status === "failed" && <p style={{ color: "red" }}>{error}</p>}
{status === "succeeded" && (
<ul>
{items.map((p) => (
<li key={p.name}>{p.name}</li>
))}
</ul>
)}
</div>
);
}
export default App;

🧭 Recap

ConceptPurpose
createAsyncThunkHandles async actions cleanly
pending / fulfilled / rejectedRepresent API call states
status & error in UIMake the app feel responsive & reliable

This pattern will be used again and again as our app grows.



Tags

#redux

Share

Related Posts

Redux Toolkit
🚀 CI/CD: The Engine Behind Modern Software Delivery
December 13, 2025
2 min
© 2025, All Rights Reserved.
Powered By

Social Media

githublinkedinyoutube