How Redux Store Enhancers Work and When to Use applyMiddleware

Redux store enhancers are higher-order functions that wrap the createStore factory to add capabilities like middleware or DevTools integration, and you should use applyMiddleware when you need to handle asynchronous logic, logging, or side effects in your action dispatching pipeline.

Redux store enhancers provide the architectural extension point for modifying core store behavior in the reduxjs/redux repository. While the base createStore function only handles synchronous state updates, enhancers allow you to inject middleware, time-travel debugging, and other cross-cutting concerns without modifying your reducer logic.

What Is a Redux Store Enhancer?

A store enhancer is fundamentally a higher-order function that accepts a store creator and returns a new, augmented store creator. According to the type definitions in src/types/store.ts, the signature follows this pattern:

type StoreEnhancer<Ext = {}, StateExt = {}> = (
  next: StoreEnhancerStoreCreator<Ext, StateExt>
) => StoreEnhancerStoreCreator<Ext, StateExt>

When invoked, the enhancer receives the original createStore as the next argument. It can then:

  • Add extra methods or state properties to the returned store object
  • Replace or wrap the dispatch function to intercept actions
  • Modify the subscription behavior or add external integrations

How applyMiddleware Implements the Enhancer Pattern

The applyMiddleware function in src/applyMiddleware.ts is the canonical implementation of a store enhancer. It transforms the dispatch pipeline by composing middleware functions into a chain. The implementation follows five distinct steps:

1. Create the Base Store

First, applyMiddleware invokes the original createStore to generate the base store object containing dispatch, getState, and subscribe.

2. Build the Middleware API

It constructs a middleware API object containing getState and a temporary dispatch function. This temporary dispatch throws if called during middleware setup, preventing circular dispatches before the chain is fully constructed.

3. Map Middlewares to the API

Each middleware function receives the middleware API, producing a chain of functions that accept next and return action => result.

4. Compose the Chain

Using the compose utility from src/compose.ts, the array of middleware functions is composed right-to-left into a single function. This composition wraps the original store.dispatch with each middleware layer.

5. Return the Enhanced Store

Finally, applyMiddleware returns a new store object that spreads the original store properties and replaces dispatch with the composed middleware chain.

When to Use applyMiddleware in Your Redux Application

You should use applyMiddleware when you need to intercept, transform, or handle side effects in the action dispatching pipeline. Common scenarios include:

  • Asynchronous logic: Middleware like Redux Thunk allows dispatching functions or promises instead of plain objects, enabling API calls and delayed dispatches.
  • Logging and instrumentation: A logger middleware can inspect every action and the resulting state for debugging purposes.
  • Error handling: Middleware can catch exceptions from action creators or reducers and report them to external monitoring services.
  • Conditional behavior: You can dynamically build the middleware array based on process.env.NODE_ENV to include development-only tools.

If you are using Redux Toolkit, you typically do not call applyMiddleware directly. The configureStore function handles this automatically while adding sensible defaults, though you can still pass a custom middleware array to configureStore which internally uses applyMiddleware.

Correct Enhancer Composition Order

When composing multiple store enhancers, applyMiddleware must be the first enhancer in the composition chain. This ordering is critical because middleware can be asynchronous—if DevTools or other enhancers wrap the store first, they will only see the raw dispatch function and miss any asynchronous actions processed by middleware.

The correct pattern uses compose from src/compose.ts to chain enhancers right-to-left:

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
import { devToolsEnhancer } from 'redux-devtools-extension';

const enhancer = compose(
  applyMiddleware(thunk), // Must be first
  devToolsEnhancer()
);

const store = createStore(rootReducer, enhancer);

Practical Code Examples

Basic Logger Middleware

This example demonstrates a simple logging middleware that prints actions and state changes:

import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers';

function logger({ getState }) {
  return next => action => {
    console.log('dispatching', action);
    const result = next(action);
    console.log('next state', getState());
    return result;
  };
}

const store = createStore(rootReducer, applyMiddleware(logger));
store.dispatch({ type: 'ADD_TODO', text: 'Learn Redux enhancers' });

Async Thunk Middleware

Using Redux Thunk for asynchronous API calls:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const store = createStore(rootReducer, applyMiddleware(thunk));

function fetchUser(id) {
  return dispatch => {
    dispatch({ type: 'FETCH_USER_REQUEST', id });
    return fetch(`/api/users/${id}`)
      .then(response => response.json())
      .then(user => dispatch({ type: 'FETCH_USER_SUCCESS', user }))
      .catch(error => dispatch({ type: 'FETCH_USER_FAILURE', error }));
  };
}

store.dispatch(fetchUser(42));

Composing Multiple Enhancers

Combining middleware with Redux DevTools:

import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(
  rootReducer,
  composeEnhancers(
    applyMiddleware(thunk),
    // other enhancers here
  )
);

Summary

  • A Redux store enhancer is a higher-order function that wraps createStore to add capabilities like middleware or DevTools integration.
  • applyMiddleware is itself a store enhancer that composes middleware functions to intercept and transform dispatched actions.
  • The implementation in src/applyMiddleware.ts creates a middleware API, maps middlewares, composes them using src/compose.ts, and returns an enhanced store with a wrapped dispatch.
  • Use applyMiddleware when handling asynchronous logic, logging, error handling, or other cross-cutting concerns in the dispatch pipeline.
  • When composing multiple enhancers, always place applyMiddleware first in the chain to ensure asynchronous middleware works correctly with DevTools and other enhancers.

Frequently Asked Questions

What is the difference between a Redux store enhancer and middleware?

A store enhancer is a higher-order function that wraps the entire createStore function to modify the store instance itself, such as adding new methods or replacing dispatch. Middleware is a specific pattern used within the dispatch pipeline that intercepts individual actions. applyMiddleware is the store enhancer that enables middleware by composing middleware functions into the dispatch chain.

Can I use multiple store enhancers together?

Yes, you can compose multiple enhancers using the compose utility from src/compose.ts. When combining enhancers, always place applyMiddleware first in the composition chain (rightmost in the compose arguments). This ensures that asynchronous middleware processes actions before DevTools or other enhancers observe the dispatch.

Do I need to use applyMiddleware with Redux Toolkit?

No, you typically do not call applyMiddleware directly when using Redux Toolkit. The configureStore function automatically sets up applyMiddleware with sensible defaults, including the Redux Thunk middleware. However, you can still customize the middleware array by passing a middleware option to configureStore, which internally uses applyMiddleware to compose your custom middleware chain.

Why does applyMiddleware throw if dispatch is called during setup?

The applyMiddleware implementation in src/applyMiddleware.ts provides a temporary dispatch function to the middleware API during initialization. This temporary function throws an error if invoked because the middleware chain has not yet been fully composed. This prevents circular dispatch scenarios where middleware might attempt to dispatch actions during the setup phase before the final store is ready.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →