State management in React applications can quickly become complex—especially when handling asynchronous logic like API calls, polling, timeouts, and background tasks. While Redux Thunk is great for simple logic, once your app grows, you’ll want something more structured and testable.
This is where Redux-Saga shines. It allows you to manage async flows using Generator functions, making your side effects:
✅ Predictable ✅ Testable ✅ Scalable ✅ Maintainable
In this blog, we’ll learn Redux-Saga step-by-step—from beginner concepts to advanced patterns.
Redux-Saga is a middleware for Redux that uses ES6 Generators to handle side effects. The main idea is to write async code that looks synchronous.
import { call, put, takeEvery } from "redux-saga/effects";
Think of sagas as “background workers” that listen to Redux actions and react to them.
npm install redux-saga
import createSagaMiddleware from "redux-saga";import { configureStore } from "@reduxjs/toolkit";import rootSaga from "./sagas";const sagaMiddleware = createSagaMiddleware();const store = configureStore({reducer: rootReducer,middleware: (getDefault) => getDefault().concat(sagaMiddleware),});sagaMiddleware.run(rootSaga);export default store;
Let’s say you want to fetch Pokémon details.
export const fetchPokemonRequest = (name) => ({type: "FETCH_POKEMON_REQUEST",payload: name,});export const fetchPokemonSuccess = (data) => ({type: "FETCH_POKEMON_SUCCESS",payload: data,});export const fetchPokemonFailure = (error) => ({type: "FETCH_POKEMON_FAILURE",error,});
import { call, put, takeEvery } from "redux-saga/effects";import axios from "axios";function* fetchPokemonSaga(action) {try {const response = yield call(axios.get,`https://pokeapi.co/api/v2/pokemon/${action.payload}`);yield put(fetchPokemonSuccess(response.data));} catch (e) {yield put(fetchPokemonFailure(e.message));}}export default function* pokemonSaga() {yield takeEvery("FETCH_POKEMON_REQUEST", fetchPokemonSaga);}
| Effect | Purpose | Example |
|---|---|---|
call(fn, ...args) | Call a function | yield call(api.getUser, id) |
put(action) | Dispatch Redux action | yield put({ type: "DONE" }) |
take(pattern) | Wait for specific action | yield take("LOGIN") |
takeEvery(pattern, saga) | Run saga for every action | yield takeEvery("FETCH", saga) |
takeLatest(pattern, saga) | Only run most recent version | yield takeLatest("SEARCH", saga) |
delay(ms) | Sleep/pause | yield delay(1000) |
Imagine you want to auto refresh data every 10 seconds while a page is open.
race, delay, and takeimport { call, put, delay, race, take, takeLatest } from "redux-saga/effects";import axios from "axios";function* autoRefreshPokemon(action) {try {while (true) {const response = yield call(axios.get,`https://pokeapi.co/api/v2/pokemon/${action.payload}`);yield put(fetchPokemonSuccess(response.data));// refresh every 10syield delay(10000);}} finally {console.log("Stopped refreshing");}}function* watchAutoRefresh() {while (true) {const action = yield take("START_REFRESH");yield race([call(autoRefreshPokemon, action),take("STOP_REFRESH"), // stop when user leaves page]);}}
This pattern ensures:
✅ Continues refreshing ✅ Cancels when user leaves ✅ No memory leaks
race — Competing TasksUseful for timeout logic:
const { response, timeout } = yield race({response: call(api.fetchData),timeout: delay(5000),});if (timeout) yield put(showError("Request Timeout"));
cancelled() — Cleanup when Saga Endsimport { cancelled } from "redux-saga/effects";function* taskSaga() {try {yield call(longRunningTask);} finally {if (yield cancelled()) {console.log("Saga was cancelled, cleaning up…");}}}
all() — Run Sagas in Parallelimport { all } from "redux-saga/effects";import pokemonSaga from "./pokemonSaga";import userSaga from "./userSaga";export default function* rootSaga() {yield all([pokemonSaga(), userSaga()]);}
Use Saga if your app needs:
| Need | Saga Helps? |
|---|---|
| Complex async flows | ✅ Excellent |
| Websocket / long polling | ✅ Best choice |
| Cancelable operations | ✅ Built-in support |
| Easy unit testing | ✅ Very easy |
If your app just needs simple API calls → use Redux Thunk instead.
Redux-Saga may feel different at first because of generator functions—but once you understand the pattern, it becomes one of the cleanest and most powerful tools for managing complex async behavior.
You now know:
✅ How to create beginner-level sagas
✅ How to use effects (call, put, takeLatest, etc.)
✅ How to handle polling + cancel flows
✅ How to manage advanced race conditions and cleanup