Module 2: The write path · Lesson 5 of 7 · 3 min read
Middleware: the pipeline between dispatch and reducer
Middleware is where side effects live. Learn the store => next => action shape, why order matters, and how sagas, effects and thunks play the role.
The shape
Middleware has one signature: store => next => action => { … }. Inside it, you can read the state, dispatch new actions, inspect or rewrite the current one, delay it, or stop it altogether. Calling next(action) passes the action on to the next middleware, and from the last one to the reducer. The Middleware doc compares it to airport security: every passenger goes through the same checkpoints, in the same order, before boarding.
const logger = (store) => (next) => (action) => {
console.log('dispatching', action.type);
const result = next(action); // the rest of the chain, then the reducer
console.log('next state', store.getState());
return result;
};
Four spellings of one job
Each template creates a todo on the server and then updates the store, and each does it through its own form of middleware:
- Saga. In
templates/chota-react-saga/src/state/todo/todo.operations.js,watchTodoscallstakeLatest(CREATE_TODO, addTodos). TheaddTodosgeneratorcalls the API, thenputscreateTodoSuccessorcreateTodoError. The saga middleware is attached instate/index.jswithapplyMiddleware(sagaMiddleware). - NgRx. In
templates/chota-angular-ngrx/src/app/state/todo/todo.effects.ts, an effect built fromcreateEffect,ofTypeandswitchMapdoes the work, registered withprovideEffects. - Pinia. There is no middleware layer.
templates/chota-vue-pinia/src/state/todo/todo.operations.jsis anasyncstore action. - RTK.
configureStorealready includes thunk middleware plus immutability and serialisability checks. To add your own middleware, extend the defaults withgetDefault().concat(...)rather than replacing them.
Order and next()
The practice quiz is built on the doc’s three Common Mistakes:
- Forgetting
next(action). The action stops there. The reducer never sees it, and the UI silently does nothing. - Using
next()for a new action.nextcarries on from the current position, so middleware earlier in the chain never sees the new action.store.dispatch()starts it at the top. Usedispatchfor new actions andnextto pass the current one along. Re-dispatching the current action withdispatchsends it back through this same middleware, which loops forever. - Mutating the action. After
action.payload.id = Date.now(), every later middleware receives a different object from the one that was dispatched. Create a new action instead.
Order matters because the chain works like an onion: the action passes in through every layer, then control unwinds back out. With applyMiddleware(logger, thunk), the logger runs before thunk, so it receives the raw thunk function instead of an action. That is why the skill says to put the logger last.
Review checklist
- Side effects such as fetch calls, storage and analytics live in middleware, sagas, effects or thunks, never in a reducer or a component.
- Every middleware calls
next(action), unless blocking the action is the documented intent. - New actions go out through
store.dispatch, and the current action is passed on withnext. - On RTK, the default middleware is extended with
getDefault().concat(), not replaced. - Every async path ends in either a success action or an error action.
Encode it, then practise it
SKILL.md that encodes Middleware for a coding agent. Install it into your agent, or download it for any Agent-Skills-compatible runtime.
npx skills add grvpanchal/elegant --skill state-middleware
Practise it
What order does middleware run in?
Four questions on the middleware chain — the part of a Redux store that is a pipeline, not a switch.
Course contents (7 lessons)
Module 1: Where state lives
Module 2: The write path
- Actions: describe what happened, not what to set done
- Reducer: the only place state changes done
- Middleware: the pipeline between dispatch and reducer done
Module 3: The read path and async