Suggest an editImprove this articleRefine the answer for “Redux middleware”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Redux middleware** intercepts actions between `dispatch` and the reducer, letting you inspect, modify, delay, or cancel them. ```javascript const loggerMiddleware = store => next => action => { console.log('dispatching:', action); const result = next(action); console.log('new state:', store.getState()); return result; }; const store = createStore(reducer, applyMiddleware(loggerMiddleware)); ``` **Key:** call `next(action)` to pass the action forward; skip it to block the action entirely.Shown above the full answer for quick recall.Answer (EN)Image**Redux middleware** is a function that sits between `dispatch` and the reducer, intercepting actions before they reach the store. ## Theory ### TL;DR - Middleware is like a mail sorting facility: actions arrive, get processed or redirected, then continue to the reducer - Signature: `store => next => action => result` (three curried functions nested inside each other) - Call `next(action)` to pass the action forward; skip that call entirely to block the action - Use middleware for side effects (async calls, logging, analytics); use reducers for state changes - Order matters: `applyMiddleware(thunkMiddleware, loggerMiddleware)` runs thunk first ### Quick Example ```javascript // Middleware signature: store => next => action => result const loggerMiddleware = store => next => action => { console.log('dispatching:', action); const result = next(action); // pass to next middleware or reducer console.log('new state:', store.getState()); return result; }; const store = createStore(rootReducer, applyMiddleware(loggerMiddleware)); // Output: "dispatching: {type: 'ADD_TODO', payload: 'Learn Redux'}" // Output: "new state: {todos: [{id: 1, text: 'Learn Redux'}]}" ``` `next(action)` hands control to the next middleware in the chain. After it returns, you can read the updated state. That is the whole pattern. ### How the Middleware Chain Works When you call `dispatch(action)`, Redux does not send it straight to the reducer. It passes it through each middleware from left to right. Each middleware is a higher-order function: the first call receives `store`, the second receives `next` (a reference to the next middleware, or the final dispatch if it is last in line), and the third receives the actual `action`. If a middleware calls `next(action)`, the action moves forward. If it skips that call, the action stops there and never reaches the reducer. Redux builds this chain once during `createStore()` by wrapping the original dispatch with `applyMiddleware()`. The curried structure exists for a specific reason. `applyMiddleware` partially applies `store` to each middleware during setup, then chains the results together. The action-handling logic only runs at dispatch time, not during store initialization. ### When to Use Middleware Side effects belong in middleware, not in reducers. Reducers must be pure functions. - Async operations (API calls, timers): use Redux Thunk or Redux Saga - Logging and debugging: log every action and state change in development builds - Analytics: intercept specific action types and send events to a tracking service - Error handling: catch and transform errors before they hit the reducer - Action transformation: add a timestamp or user ID to every outgoing action - Cancellation: block actions from reaching the reducer based on current state or conditions ### Common Mistakes **Calling `next()` twice** ```javascript // WRONG - action reaches reducer twice const badMiddleware = store => next => action => { next(action); next(action); }; // RIGHT - call next() exactly once const goodMiddleware = store => next => action => { console.log('before:', action); const result = next(action); console.log('after:', store.getState()); return result; }; ``` **Forgetting to return the result** ```javascript // WRONG - breaks the chain for subscribers const badMiddleware = store => next => action => { next(action); // missing return }; // RIGHT const goodMiddleware = store => next => action => { return next(action); }; ``` **Mutating the action object** ```javascript // WRONG - mutates the original action const badMiddleware = store => next => action => { action.timestamp = Date.now(); return next(action); }; // RIGHT - spread into a new object const goodMiddleware = store => next => action => { return next({ ...action, timestamp: Date.now(), userId: store.getState().auth.userId }); }; ``` **Getting middleware order wrong** ```javascript // WRONG - logger runs before thunk resolves async actions const store = createStore( reducer, applyMiddleware(loggerMiddleware, thunkMiddleware) ); // RIGHT - thunk converts function actions first, then logger sees plain objects const store = createStore( reducer, applyMiddleware(thunkMiddleware, loggerMiddleware) ); ``` **Dispatching async functions without Thunk** ```javascript // WRONG - dispatch expects a plain object dispatch(async () => { const data = await fetch('/api/data'); // this function never executes }); // RIGHT - add thunk middleware first const store = createStore(reducer, applyMiddleware(thunk)); dispatch((dispatch) => { fetch('/api/data').then(data => dispatch({ type: 'SET_DATA', payload: data })); }); ``` ### Real-World Usage - **Redux Thunk** (most common): dispatch functions that contain async API calls - **Redux Saga**: complex flows with cancellation and race conditions, used in large apps like Uber's frontend - **Redux Observable**: RxJS-based, for reactive stream operations - **Redux Logger**: logs every action and state diff in development builds - **Redux Persist**: intercepts all actions to sync state with localStorage automatically - **Custom analytics middleware**: catches specific action types and sends them to a tracking service ### Follow-Up Questions **Q:** Why is middleware a curried function (`store => next => action => result`) instead of a regular function? **A:** Currying lets Redux partially apply arguments during setup. `applyMiddleware` calls the outer function with `store` once, then chains the resulting functions together. The action-handling logic only runs at dispatch time, not during store creation. **Q:** What happens if a middleware does not call `next()`? **A:** The action stops propagating. It never reaches the reducer or any middleware after it. Subscribers still get notified of the dispatch call, which can cause unexpected behavior if state did not actually change. **Q:** Can middleware read the current state? **A:** Yes. `store.getState()` is available inside the middleware. Call it before `next(action)` to see the state before processing, or after to see the updated state. **Q:** What is the difference between Redux Thunk and Redux Saga? **A:** Thunk is simpler: it lets you dispatch functions instead of plain objects. Saga uses generator functions and handles complex flows like cancellation and race conditions. Use Thunk for straightforward async calls; use Saga when you need to coordinate multiple requests or cancel in-flight ones. **Q:** (Senior-level) How would you prevent race conditions when the same async action is dispatched multiple times rapidly? **A:** Track pending requests by action type inside the middleware. If the same type is already in-flight, ignore or queue the new dispatch. Redux Saga handles this with `takeLatest()`, which automatically cancels the previous request when a new one arrives. ## Examples ### Basic: Logger Middleware ```javascript const loggerMiddleware = store => next => action => { console.log('dispatching:', action.type); const result = next(action); console.log('new state:', store.getState()); return result; }; const store = createStore(rootReducer, applyMiddleware(loggerMiddleware)); store.dispatch({ type: 'ADD_TODO', payload: 'Learn Redux' }); // dispatching: ADD_TODO // new state: { todos: [{ id: 1, text: 'Learn Redux' }] } ``` Simple to write, useful in development. Add it after thunk in `applyMiddleware` so it sees already-resolved actions. ### Intermediate: Async Middleware (Thunk Pattern) ```javascript const thunkMiddleware = store => next => action => { // if the action is a function, call it with dispatch and getState if (typeof action === 'function') { return action(store.dispatch, store.getState); } return next(action); }; const fetchUser = (userId) => async (dispatch, getState) => { dispatch({ type: 'FETCH_USER_START' }); try { const response = await fetch(`/api/users/${userId}`); const user = await response.json(); dispatch({ type: 'FETCH_USER_SUCCESS', payload: user }); } catch (error) { dispatch({ type: 'FETCH_USER_ERROR', payload: error.message }); } }; // In a component: store.dispatch(fetchUser(123)); // dispatches FETCH_USER_START immediately, // then FETCH_USER_SUCCESS or FETCH_USER_ERROR when the request settles ``` Thunk checks if the action is a function. If yes, it calls that function with `dispatch` and `getState`. If no, it passes the action through as normal. That is the entire implementation. ### Advanced: Deduplication Middleware ```javascript const dedupeMiddleware = store => { let lastAction = null; let lastTime = 0; return next => action => { const now = Date.now(); const isDuplicate = lastAction?.type === action.type && lastAction?.payload === action.payload && now - lastTime < 500; // same action within 500ms if (isDuplicate) { console.warn('Duplicate action blocked:', action.type); return; // skip next() entirely } lastAction = action; lastTime = now; return next(action); }; }; // dispatch({ type: 'SUBMIT_FORM', payload: formData }) twice within 500ms // Second dispatch is blocked. Only one action reaches the reducer. ``` Note the closure over `lastAction` and `lastTime`. The middleware keeps that state across dispatches because the outer function runs once during store setup. This pattern came up in production when handling double-submit bugs on slow network connections: the form button fires twice, but only one request goes through.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.