Async State Management in React – Thunk, Saga & RTK Query (2025 Guide)
Modern React applications often need to fetch data from APIs, handle async operations, and manage side effects.
While Redux provides predictable state management, handling async actions requires additional tools:
Redux Thunk – simplest way to handle async logic
Redux Saga – powerful for complex side effects
RTK Query – modern Redux Toolkit approach for API fetching
In this tutorial, we’ll explore each method, provide examples, and discuss when to use them.
Redux Thunk
Redux Thunk allows you to write async logic that interacts with the Redux store.
Installation
npm install redux-thunkExample: Fetching Data with Thunk
// actions.js
export const fetchUsers = () => async (dispatch) => {
dispatch({ type: "FETCH_USERS_REQUEST" });
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await response.json();
dispatch({ type: "FETCH_USERS_SUCCESS", payload: data });
} catch (error) {
dispatch({ type: "FETCH_USERS_FAILURE", payload: error });
}
};
Reducer Example:
const initialState = { users: [], loading: false, error: null };
export const usersReducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_USERS_REQUEST":
return { ...state, loading: true };
case "FETCH_USERS_SUCCESS":
return { ...state, loading: false, users: action.payload };
case "FETCH_USERS_FAILURE":
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
Usage in React:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchUsers } from "./actions";
function UserList() {
const dispatch = useDispatch();
const { users, loading } = useSelector((state) => state.users);
useEffect(() => {
dispatch(fetchUsers());
}, [dispatch]);
if (loading) return <p>Loading...</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Thunk is simple and easy to learn, perfect for small-to-medium apps.
Redux Saga
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.
Installation
npm install redux-sagaExample: Fetching Data with Saga
import { call, put, takeEvery } from "redux-saga/effects";
function* fetchUsersSaga() {
try {
const response = yield call(fetch, "https://jsonplaceholder.typicode.com/users");
const data = yield response.json();
yield put({ type: "FETCH_USERS_SUCCESS", payload: data });
} catch (error) {
yield put({ type: "FETCH_USERS_FAILURE", payload: error });
}
}
export function* watchFetchUsers() {
yield takeEvery("FETCH_USERS_REQUEST", fetchUsersSaga);
}
Reducer: Same as Thunk example.
Usage: Dispatch FETCH_USERS_REQUEST to trigger saga.
Sagas are powerful for complex async flows, like retries, debouncing, and background tasks.
Redux Toolkit Query (RTK Query)
RTK Query is part of Redux Toolkit and provides automatic caching, fetching, and updating for APIs.
Installation
npm install @reduxjs/toolkit react-reduxExample: Creating an API Slice
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl: "https://jsonplaceholder.typicode.com/" }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => "users",
}),
}),
});
export const { useGetUsersQuery } = api;
Using RTK Query in Component
function UserList() {
const { data: users, error, isLoading } = useGetUsersQuery();
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error loading users.</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
RTK Query reduces boilerplate, automatically manages caching, loading, and error states.
Comparison: Thunk vs Saga vs RTK Query
| Feature | Thunk | Saga | RTK Query |
|---|---|---|---|
| Complexity | Low | Medium-High | Low |
| Boilerplate | Medium | High | Minimal |
| Async Handling | Simple API calls | Complex side effects | Built-in fetch + cache |
| Learning Curve | Easy | Hard | Easy |
| Best For | Small apps | Complex async flows | Most modern apps |