What Is Redux Saga
Redux Saga is middleware that sits between dispatch and the reducer, handling all server interactions before a plain action ever reaches the reducer.
Generator functions give Redux Saga its execution model. Now for the question those generators are actually solving.
I kept reading that Redux Saga "handles side effects" without understanding what that phrase meant inside a Redux app. It clicked once I saw where Saga lives in the data flow: not inside a component, not inside a reducer, but in the gap between dispatch and the reducer.
What Is a Side Effect?
A side effect is any interaction that goes outside the pure data flow: an HTTP request to a server, writing to localStorage, reading from sessionStorage, sending a message over a WebSocket. These cannot go inside a reducer because reducers must be pure functions -- deterministic, synchronous, no I/O.
Redux Saga takes all of that async work out of both components and reducers and handles it in a dedicated layer.
Where Saga Lives in the Redux Flow
In plain Redux, the path from user interaction to state update is direct: component dispatches an action, the reducer handles it, state changes, component re-renders.
Saga inserts itself into that path. When an action is dispatched, Saga middleware checks whether any running saga is watching for that type. If it is, Saga handles the async work -- makes the HTTP request, waits for the response -- then dispatches a new plain action. That plain action goes to the reducer as normal.
ExpandRedux Saga in the data flow: action dispatched, saga intercepts, HTTP request made, result action dispatched, reducer processes, state updated
The reducer never touches the network. It receives a plain object with the response data and processes it synchronously, exactly like any other action. Saga owns the async gap; the reducer owns the state update.
How It Compares to Redux Thunk
Redux Thunk also handles async work. The difference is the level of control.
With Thunk, the async logic lives inside action creators that receive dispatch. For a single API call this works cleanly. For anything more concurrent it gets complicated.
Saga adds built-in tools for the hard cases:
- Debouncing: the user clicks repeatedly, Saga waits until they stop before firing
- Cancellation: a request is in flight, the user navigates away, Saga cancels it
- Concurrency control:
takeEveryallows multiple concurrent requests,takeLatestcancels the previous one
For simple and medium applications, Redux Thunk is the right default. The added setup cost of Saga is only worth it when concurrent behavior and fine-grained cancellation are real requirements.
The next step is adding Saga to the store -- three specific lines that wire the middleware in before any saga can run.
The Essentials
- A side effect is any interaction with the outside world. HTTP requests, localStorage, sockets -- these cannot go inside reducers because reducers must stay pure and synchronous.
- Saga sits between dispatch and the reducer. Matching actions are intercepted, the async work runs, and a plain result action is dispatched. The reducer receives a clean, synchronous action.
- Saga vs Thunk is about scale. Thunk works well for simple async. Saga's built-in debouncing, cancellation, and concurrency control make it the right choice when those requirements actually exist.
Further Reading and Watching
- Understanding Generator Functions & Using Redux Saga (Fullstack Academy): builds the mental model from generators through to actual saga usage in a single walkthrough
- Redux Saga Introduction: the official getting-started guide with the canonical counter example