Redux Saga
Redux-Saga is a middleware library for Redux, designed to manage application side effects (like asynchronous data fetching, accessing the browser cache, or impure actions) in a more organized and testable manner. It leverages ES6 Generators to make asynchronous flows look like synchronous code, improving readability and maintainability. The current stable version is 1.4.2, with patch releases occurring relatively frequently and minor versions every few months. Its key differentiators include its declarative effect model, powerful concurrency control patterns (e.g., `takeEvery`, `takeLatest`), and robust testing utilities, providing a structured alternative to Redux Thunk for complex side effect management.
Common errors
-
TypeError: createSagaMiddleware is not a function
cause `createSagaMiddleware` is typically a default export from `redux-saga`.fixChange your import statement to `import createSagaMiddleware from 'redux-saga';` instead of `import { createSagaMiddleware } from 'redux-saga';`. -
Error: takeEvery(pattern, saga): saga argument must be a Generator function!
cause The second argument passed to `takeEvery` (or similar effect creators) must be a generator function (e.g., `function* mySaga() { ... }`).fixEnsure the saga function passed to `takeEvery` is declared as a generator function, e.g., `function* myWorkerSaga() { yield put({ type: 'ACTION' }); }`. -
TS2307: Cannot find module 'redux-saga/effects' or its corresponding type declarations.
cause This error often indicates incorrect `moduleResolution` in `tsconfig.json` or issues with package installation/bundler configuration, preventing TypeScript from finding the submodule types.fixVerify that `redux-saga` is correctly installed. Check your `tsconfig.json`'s `compilerOptions.moduleResolution` (e.g., try 'bundler' or 'node') and ensure `node_modules/@redux-saga/types` is accessible. Upgrading to `redux-saga@1.4.1` or newer might also help if you are on an older version with this issue. -
effects must be plain objects, received [object Promise]
cause This error occurs when you use `await` or return a Promise directly from a saga without yielding an effect, or if you're not yielding a proper `redux-saga` effect object.fixInside a saga, always `yield` effect creators from `redux-saga/effects` (e.g., `yield call(api.fetchData)` or `yield put({ type: 'ACTION' })`). Do not use `await` directly; use `yield call` for promise-based functions.
Warnings
- breaking The `exports` field was added to `package.json` in v1.4.0. This change restricts what files can be directly imported from the package. While public APIs are maintained, any prior deep imports to non-public files might break.
- gotcha When using TypeScript, incorrect `moduleResolution` settings in `tsconfig.json` (e.g., 'bundler' or 'node') can lead to type compatibility issues, especially with older versions or specific bundler configurations.
- breaking Prior to v1.0.0, the API for `createSagaMiddleware` and effect creators had different signatures and argument orders. Migrating from older `0.x` versions requires careful review of the migration guide.
- gotcha Using `sagaMiddleware.run()` multiple times with the same saga instance can lead to unintended behavior, as the saga will be started again. It's typically meant to be called once per root saga.
Install
-
npm install redux-saga -
yarn add redux-saga -
pnpm add redux-saga
Imports
- createSagaMiddleware
import { createSagaMiddleware } from 'redux-saga';import createSagaMiddleware from 'redux-saga';
- takeEvery
import { takeEvery } from 'redux-saga';import { takeEvery, put, call } from 'redux-saga/effects'; - Saga
import { Saga } from 'redux-saga';import type { Saga } from 'redux-saga';
Quickstart
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { takeEvery, put, call } from 'redux-saga/effects';
// Reducer
const counterReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
// Sagas
function* incrementAsync() {
yield call(delay, 1000); // Simulate an async operation
yield put({ type: 'INCREMENT' });
}
function* delay(ms) {
return new Promise(res => setTimeout(res, ms));
}
function* rootSaga() {
yield takeEvery('INCREMENT_ASYNC', incrementAsync);
}
// Create saga middleware
const sagaMiddleware = createSagaMiddleware();
// Create Redux store
const store = createStore(
counterReducer,
applyMiddleware(sagaMiddleware)
);
// Run sagas
sagaMiddleware.run(rootSaga);
// Dispatch actions
store.dispatch({ type: 'INCREMENT_ASYNC' });
store.dispatch({ type: 'INCREMENT' });
// Log state changes
store.subscribe(() => console.log(store.getState()));