0Pricing
React Academy · Lesson

Async Logic with createAsyncThunk

Handle API calls in Redux with createAsyncThunk and the pending/fulfilled/rejected lifecycle.

Async Logic with createAsyncThunk is a free React Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the React Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Welcome

In this lesson you will handle API calls in Redux with createAsyncThunk and handle the pending, fulfilled, and rejected lifecycle actions in your slice.

What Is createAsyncThunk?

createAsyncThunk generates a thunk action creator that dispatches three lifecycle actions automatically: pending (started), fulfilled (resolved), and rejected (failed). You handle each in extraReducers.

Defining a Thunk

Call createAsyncThunk with a type string and an async payload creator function. Return the data you want as the fulfilled payload.
import { createAsyncThunk } from '@reduxjs/toolkit';

export const fetchUser = createAsyncThunk(
  'users/fetchById',
  async (userId) => {
    const response = await fetch('/api/users/' + userId);
    return response.json(); // becomes action.payload on fulfilled
  }
);

Handling in extraReducers

Add cases for all three lifecycle states in the slice's extraReducers. Manage loading, data, and error state accordingly.
extraReducers: (builder) => {
  builder
    .addCase(fetchUser.pending, state => {
      state.loading = true;
      state.error = null;
    })
    .addCase(fetchUser.fulfilled, (state, action) => {
      state.loading = false;
      state.user = action.payload;
    })
    .addCase(fetchUser.rejected, (state, action) => {
      state.loading = false;
      state.error = action.error.message;
    });
}

Dispatching the Thunk

Dispatch the thunk from a component with useDispatch. Pass arguments directly to the thunk action creator.
const dispatch = useDispatch();
useEffect(() => {
  dispatch(fetchUser(userId));
}, [userId]);

Accessing State in the Thunk

The payload creator receives a thunkAPI object as its second argument. Use thunkAPI.getState() to read current Redux state and thunkAPI.dispatch() to dispatch other actions.
export const saveItem = createAsyncThunk(
  'items/save',
  async (item, { getState }) => {
    const { auth } = getState();
    return fetch('/api/items', {
      method: 'POST',
      headers: { Authorization: 'Bearer ' + auth.token },
      body: JSON.stringify(item),
    }).then(r => r.json());
  }
);

Handling Errors with rejectWithValue

To pass a custom error payload to the rejected case, use thunkAPI.rejectWithValue(). The rejected action's action.payload will contain your custom error object.
export const fetchData = createAsyncThunk(
  'data/fetch',
  async (id, { rejectWithValue }) => {
    const res = await fetch('/api/' + id);
    if (!res.ok) return rejectWithValue({ status: res.status });
    return res.json();
  }
);
// In rejected case: action.payload.status is available

Cancelling Thunks

Pass an AbortController signal to your fetch inside the thunk. createAsyncThunk provides signal via thunkAPI.signal automatically when the thunk is cancelled with dispatch.abort().
async (id, { signal }) => {
  const res = await fetch('/api/' + id, { signal });
  return res.json();
}

Awaiting a Dispatched Thunk

dispatch() of a thunk returns a Promise. You can await it to know when the async work completes, useful for redirect-after-save patterns.
const result = await dispatch(saveItem(data));
if (saveItem.fulfilled.match(result)) {
  navigate('/success');
}

Builder Notation vs Object Notation

Use the builder.addCase callback pattern in extraReducers — it is type-safe and supported. The older object-key notation is deprecated in recent RTK versions.

Quick Check

What happens when the async function inside createAsyncThunk throws an error?

Recap

createAsyncThunk generates pending/fulfilled/rejected actions automatically. Handle all three in extraReducers. Use rejectWithValue for custom error payloads and thunkAPI.getState() to access current state inside thunks.

Up Next

Next lesson: **RTK Query: Endpoints & Auto-Caching** — you will define API endpoints with createApi and let RTK Query manage caching automatically.

Frequently asked questions

Is the “Async Logic with createAsyncThunk” lesson free?

Yes — the full text of “Async Logic with createAsyncThunk” is free to read here on the web, and the React Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the React Academy course, upgrade to CoddyKit PRO.

What will I learn in “Async Logic with createAsyncThunk”?

Handle API calls in Redux with createAsyncThunk and the pending/fulfilled/rejected lifecycle. You practise React Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start React Academy?

No prior experience is required. React Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Async Logic with createAsyncThunk” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this React Academy lesson?

Yes. Every React Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Redux Toolkit Slices & createSlice
  2. Async Logic with createAsyncThunk
  3. RTK Query: Endpoints & Auto-Caching
  4. Optimistic Updates & Cache Invalidation
← Back to React Academy