ilokesto

Composing middleware

Middleware is useful when every update should pass through the same rule: logging, validation, normalization, or instrumentation.

Add logging first

store.pushMiddleware((nextState, next) => {
  const before = store.getState();
  next(nextState);
  const after = store.getState();

  if (!Object.is(before, after)) {
    console.debug("store changed", { before, after });
  }
});

Because next() applies the rest of the chain, code after next() observes the final state unless a later middleware blocked the update.

Validate before applying

store.unshiftMiddleware((nextState, next) => {
  const resolved = typeof nextState === "function" ? nextState(store.getState()) : nextState;

  if (resolved.count < 0) {
    return;
  }

  next(resolved);
});

This middleware resolves updater functions early so it can validate the value. That means later middleware receives a value instead of the original updater. Document that choice when middleware order matters.

Choose order deliberately

  • Use unshiftMiddleware() for a rule that must see updates before existing middleware.
  • Use pushMiddleware() for a rule that should sit after existing earlier rules.
  • Remember that the first middleware in the array receives the update first because the implementation chains with reduceRight.

Keep middleware synchronous

Middleware runs inside setState(). Avoid async side effects that make ordering hard to reason about. If you need async work, start it outside the store and call setState() with the result later.

On this page