Community JavaScript Snippet
A Redux + redux-saga Wiring I Still Reach For
redux-saga still earns its keep when async workflows get tangled. The wiring I copy-paste into legacy codebases, plus the takeEvery vs takeLatest decision and the canonical fetch saga.
A Redux + redux-saga Wiring I Still Reach For
redux-saga still earns its keep when async workflows get tangled. The wiring I copy-paste into legacy codebases, plus the takeEvery vs takeLatest decision and the canonical fetch saga.
By @felixhaddad
May 11, 2026
·
Updated May 20, 2026
1,138 views
21
Rate
The order in the bootstrap is the load-bearing detail. applyMiddleware(logger, thunk, sagaMiddleware) is the conventional stack: logger sees every action first, thunk handles function-typed actions, sagaMiddleware feeds the saga runtime, and the reducer is at the end of the chain. sagaMiddleware.run(rootSaga) has to come after createStore because the run call needs the live dispatch and getState references that the middleware captured during applyMiddleware. Putting it before, or forgetting it entirely, is the bug that produces the silent failure mode where every dispatched action is logged but no saga ever fires. Mixing thunks and sagas in one app is fine; thunks for fire-and-forget side effects, sagas for anything that needs cancellation, debouncing, or coordinated multi-step workflows.
If the action is something the user fires repeatedly faster than the work completes (search-as-you-type, autosave, validate-on-keystroke), takeLatest is the right default. Each new dispatch cancels the previous in-flight worker, so the only result that lands is the latest one, which avoids the stale-response bug where a slow request for 'a' overwrites a fast result for 'abc'. takeEvery is the right default for actions where each instance is independent and you want all of them to run to completion: dispatching analytics events, recording log lines, processing items from a queue. Mixing them in one watcher is also fine: yield takeEvery('LOG_EVENT', logWorker) plus yield takeLatest('SEARCH', searchWorker) covers the typical pair of cadences in a single saga file.
Three habits make these sagas testable and resilient. First, yield call(api.fetchUser, id) instead of calling the API directly: the saga yields a description of the call, and the test runner asserts on that descriptor without ever hitting the network. Second, the try/catch around the call so any rejected promise from the API turns into a FETCH_USER_ERROR action with structured payload; the reducer can render an error banner and the rest of the app keeps moving. Third, put for both success and error so the entire side-effect surface is just "the saga dispatches actions in response to actions", which is the part that pays back when you debug a complex flow six months later. Add select for reading store slices, delay for retries, and cancelled for cleanup, and you have the saga vocabulary that covers most real workflows.
