All questions

Redux Middleware (Thunk + Logger)

Premium

Redux Middleware (Thunk + Logger)

Redux middleware is a wrapper around dispatch: it sees every action on the way to the reducer and can log it, transform it, delay it, or swallow it. Middleware are written with a curried signature — store => next => action => result — and composed into a chain, so each one hands the action to the next one and the last hands it to the store's real dispatch. Here you build a small Redux core (createStore and applyMiddleware) plus two classic middleware: thunk, which lets you dispatch a function of (dispatch, getState), and a logger that records the state around every action. If you have used Redux, this is the machinery behind applyMiddleware and redux-thunk.

Signature

type Action = { type: string };
type Dispatch = (action: Action | Function) => unknown;
type StoreAPI = { getState: () => unknown; dispatch: Dispatch };

// Every middleware is curried: store => next => action => result
type Middleware = (store: StoreAPI) => (next: Dispatch) => (action: unknown) => unknown;

const reduxMiddlewareThunkLogger: {
  // Minimal Redux core.
  createStore(reducer: Function, preloadedState?: unknown, enhancer?: Function): {
    getState(): unknown;
    dispatch: Dispatch;
    subscribe(listener: () => void): () => void; // returns an unsubscribe fn
  };
  // Turns a list of middleware into a store enhancer.
  applyMiddleware(...middlewares: Middleware[]): Function;
  // A bare middleware: dispatch a function to run it as a thunk.
  thunk: Middleware;
  // A middleware factory: pass a sink to receive each log entry.
  logger(sink?: (entry: { action: unknown; prevState: unknown; nextState: unknown }) => void): Middleware;
};

Examples

const { createStore, applyMiddleware, logger } = reduxMiddlewareThunkLogger;
const reducer = (s = { count: 0 }, a) => (a.type === 'INC' ? { count: s.count + 1 } : s);

const log = [];
const store = createStore(reducer, applyMiddleware(logger((e) => log.push(e))));
store.dispatch({ type: 'INC' });
// store.getState() → { count: 1 }
// log[0]           → { action: { type: 'INC' }, prevState: { count: 0 }, nextState: { count: 1 } }
const { createStore, applyMiddleware, thunk } = reduxMiddlewareThunkLogger;
const reducer = (s = { count: 0 }, a) => (a.type === 'INC' ? { count: s.count + 1 } : s);

const store = createStore(reducer, applyMiddleware(thunk));
const bumpTwice = (dispatch) => {
  dispatch({ type: 'INC' });
  dispatch({ type: 'INC' });
};
store.dispatch(bumpTwice);
// store.getState() → { count: 2 } — the thunk dispatched two plain actions

Notes

  • Curried on purpose — a middleware is store => next => action. The outer layers run once (store setup, then chain wiring); only the innermost action => result runs on every dispatch.
  • thunk is a middleware; logger is a factory — use thunk directly, but call logger(sink) first to configure the sink and get a middleware back.
  • Re-dispatching re-enters the chain — when a thunk calls dispatch(plainAction), that action flows through the whole middleware chain again, so the logger records it too.
  • Keep the reducer pure — do not special-case function actions inside the reducer; running thunks is the middleware's job.
  • sink defaults to a no-oplogger() with no argument must not throw.
  • No timers — a thunk may dispatch synchronously; you do not need real async here.

FAQ

Why is Redux middleware written as store then next then action?
Currying lets each layer bind at a different time: the store is bound once when the store is created, next once when the chain is wired, and the innermost action function runs on every dispatch. It also makes middleware composable, because each one returns a function of the next one.
Why does a thunk's dispatch run the whole middleware chain again?
applyMiddleware hands every middleware a dispatch that points at the fully composed chain, not the raw store. So when a thunk dispatches a plain action, it re-enters at the top of the chain and the logger and other middleware still see it.
Should the logger read state before or after next(action)?
Read prevState before calling next and nextState after. The call to next runs the rest of the chain and the reducer, so reading nextState first would capture the old state twice and the log would show no change.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium