Advanced Redux — Side Effects, Async Tasks & ThunksEntry 6 of 9

createAsyncThunk: The Modern Way

RTK's helper that wraps an async function and automatically dispatches pending, fulfilled, and rejected actions, handled in the slice's extraReducers.

Notes

  • No hand-written loading/success/failure actions. RTK generates users/fetchUsers/pending, /fulfilled, and /rejected.
  • The returned value becomes action.payload in fulfilled. A thrown error lands in action.error.message in rejected.
  • Track request state with a status field: 'idle' | 'loading' | 'succeeded' | 'failed'.
  • Guarding with status === 'idle' before dispatching prevents duplicate fetches when several components mount.
  • Use rejectWithValue to return a custom error payload instead of a plain message.

Resulting flow:

  1. Component dispatches fetchUsers().
  2. RTK dispatches users/fetchUsers/pending.
  3. The API resolves or fails; RTK dispatches fulfilled or rejected.
  4. extraReducers update state.
  5. The UI re-renders from the new state.

What / Why / When

What

Standardized async lifecycle for one request.

Why

Less boilerplate than custom thunks, consistent loading/error handling, fully typed.

When

A request maps to one piece of state (load users, load products). Use a custom thunk (Entry 5) for multi-step flows.

Code compare

React
// features/users/api.tsexport type User = { id: number; name: string };​export async function getUsers(): Promise<User[]> {  const res = await fetch("https://jsonplaceholder.typicode.com/users");  if (!res.ok) throw new Error("Failed to fetch users");  return res.json();}
Angular
// features/users/users.service.ts@Injectable({ providedIn: "root" })export class UsersService {  private http = inject(HttpClient);​  getUsers() {    return this.http.get<User[]>("https://jsonplaceholder.typicode.com/users");  }}

Differences and gotchas

  • createAsyncThunk generates three actions from one call. NgRx needs three declared actions plus an effect, but the action names document intent.
  • exhaustMap ignores new load actions while a request is in flight, which replaces the status === 'idle' guard as the main protection against duplicates.
  • In React StrictMode (development), effects run twice on mount. The idle guard plus RTK's condition option prevents a double request.

Quick reference

TaskRedux ToolkitNgRx
Define requestcreateAsyncThunk('users/fetchUsers', fn)Action group + createEffect()
Lifecycle actionsAuto: pending / fulfilled / rejectedDeclared: Load / Load Success / Load Failure
Handle in stateextraReducers + builder.addCaseon() in createReducer
Error valueaction.error.message / rejectWithValueFailure action props
Prevent duplicatesstatus === 'idle' / conditionexhaustMap