So far, we’ve used Redux Toolkit and createAsyncThunk to handle API requests. But in larger applications, you may want more control over side effects, such as:
This is where Redux-Saga shines.
In this article, we will integrate Redux-Saga to load Pokémon details from the Pokémon API when the user selects a Pokémon from the list.
Redux-Saga is a middleware for Redux that uses generator functions (function*) to handle async workflows in a more declarative way.
Instead of doing:
dispatch(fetchPokemonDetails());
and hoping your async function handles everything…
Saga lets you describe how the side effect should work:
takeLatest("pokemon/fetchDetail", fetchPokemonDetailSaga);
This is more predictable and easier to test.
npm install redux-saga
src/features/pokemonDetail/pokemonDetailSlice.js
import { createSlice } from "@reduxjs/toolkit";const pokemonDetailSlice = createSlice({name: "pokemonDetail",initialState: {data: null,loading: false,error: null,},reducers: {fetchPokemonDetail: (state) => {state.loading = true;},fetchPokemonDetailSuccess: (state, action) => {state.loading = false;state.data = action.payload;state.error = null;},fetchPokemonDetailFailure: (state) => {state.loading = false;state.error = "Failed to load Pokémon details.";},},});export const {fetchPokemonDetail,fetchPokemonDetailSuccess,fetchPokemonDetailFailure,} = pokemonDetailSlice.actions;export default pokemonDetailSlice.reducer;
Notice: This slice does not fetch data directly — the Saga will handle that.
src/features/pokemonDetail/pokemonDetailSaga.js
import { call, put, takeLatest } from "redux-saga/effects";import {fetchPokemonDetail,fetchPokemonDetailSuccess,fetchPokemonDetailFailure,} from "./pokemonDetailSlice";function* handleFetchPokemonDetail(action) {try {const response = yield call(fetch, action.payload);const data = yield response.json();yield put(fetchPokemonDetailSuccess(data));} catch (error) {yield put(fetchPokemonDetailFailure());}}export function* pokemonDetailSaga() {yield takeLatest(fetchPokemonDetail.type, handleFetchPokemonDetail);}
| Effect | Meaning |
|---|---|
takeLatest | Only run the latest request (cancel previous ones) |
call | Executes an async function |
put | Dispatches a Redux action |
Edit your store.js:
import { configureStore } from "@reduxjs/toolkit";import createSagaMiddleware from "redux-saga";import pokemonDetailReducer from "./features/pokemonDetail/pokemonDetailSlice";import { pokemonDetailSaga } from "./features/pokemonDetail/pokemonDetailSaga";const sagaMiddleware = createSagaMiddleware();export const store = configureStore({reducer: {pokemonDetail: pokemonDetailReducer,},middleware: (getDefaultMiddleware) =>getDefaultMiddleware({ thunk: false }).concat(sagaMiddleware),});sagaMiddleware.run(pokemonDetailSaga);
We disable thunk because Saga will handle async logic.
Example usage in PokemonDetail.jsx:
import { useDispatch, useSelector } from "react-redux";import { fetchPokemonDetail } from "../features/pokemonDetail/pokemonDetailSlice";import { useEffect } from "react";export function PokemonDetail({ url }) {const dispatch = useDispatch();const { data, loading, error } = useSelector((state) => state.pokemonDetail);useEffect(() => {dispatch(fetchPokemonDetail(url));}, [url, dispatch]);if (loading) return <p>Loading details...</p>;if (error) return <p style={{ color: "red" }}>{error}</p>;if (!data) return null;return (<div><h2>{data.name.toUpperCase()}</h2><img src={data.sprites.front_default} alt={data.name} /></div>);}
You now have:
When you click a Pokémon, its details load efficiently and predictably.
| Feature | Benefit |
|---|---|
takeLatest | Cancels old requests to avoid race conditions |
| Generator Functions | Easy-to-read async workflows |
Effect Helpers (call, put, etc.) | Declarative side-effect logic |
| Easy to test | Pure generator functions instead of nested callbacks |
Saga shines when your app grows and you need control.
I can now create an advanced blog about:
Auto Refresh & Cancel API Requests with Redux-Saga (for example: refresh Pokémon detail every 10 seconds and stop when leaving the page)
Just tell me:
Do you want your explanation style to be: