Predictable state container for JavaScript apps
—
Store enhancement system for adding middleware and extending store capabilities. Middleware enables async action handling, logging, routing, and other cross-cutting concerns by intercepting and potentially transforming actions before they reach reducers.
Creates a store enhancer that applies middleware to the dispatch method of the Redux store.
/**
* Creates a store enhancer that applies middleware to the dispatch method
* @param middlewares - The middleware chain to be applied
* @returns A store enhancer applying the middleware
*/
function applyMiddleware(): StoreEnhancer;
function applyMiddleware<Ext1, S>(
middleware1: Middleware<Ext1, S, any>
): StoreEnhancer<{ dispatch: Ext1 }>;
function applyMiddleware<Ext1, Ext2, S>(
middleware1: Middleware<Ext1, S, any>,
middleware2: Middleware<Ext2, S, any>
): StoreEnhancer<{ dispatch: Ext1 & Ext2 }>;
function applyMiddleware<Ext1, Ext2, Ext3, S>(
middleware1: Middleware<Ext1, S, any>,
middleware2: Middleware<Ext2, S, any>,
middleware3: Middleware<Ext3, S, any>
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 }>;
function applyMiddleware<Ext1, Ext2, Ext3, Ext4, S>(
middleware1: Middleware<Ext1, S, any>,
middleware2: Middleware<Ext2, S, any>,
middleware3: Middleware<Ext3, S, any>,
middleware4: Middleware<Ext4, S, any>
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 & Ext4 }>;
function applyMiddleware<Ext1, Ext2, Ext3, Ext4, Ext5, S>(
middleware1: Middleware<Ext1, S, any>,
middleware2: Middleware<Ext2, S, any>,
middleware3: Middleware<Ext3, S, any>,
middleware4: Middleware<Ext4, S, any>,
middleware5: Middleware<Ext5, S, any>
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 & Ext4 & Ext5 }>;
function applyMiddleware<Ext, S = any>(
...middlewares: Middleware<any, S, any>[]
): StoreEnhancer<{ dispatch: Ext }>;Usage Examples:
import { applyMiddleware, legacy_createStore as createStore } from "redux";
import thunk from "redux-thunk";
import logger from "redux-logger";
// Single middleware
const store = createStore(rootReducer, applyMiddleware(thunk));
// Multiple middleware (order matters - applied left to right)
const store = createStore(
rootReducer,
applyMiddleware(thunk, logger)
);
// With preloaded state
const store = createStore(
rootReducer,
preloadedState,
applyMiddleware(thunk, logger)
);
// Conditional middleware based on environment
const middlewares = [thunk];
if (process.env.NODE_ENV === "development") {
middlewares.push(logger);
}
const store = createStore(rootReducer, applyMiddleware(...middlewares));Composes single-argument functions from right to left for combining store enhancers and middleware.
/**
* Composes single-argument functions from right to left
* @param funcs - The functions to compose
* @returns A function obtained by composing the argument functions from right to left
*/
function compose(): <R>(a: R) => R;
function compose<F extends Function>(f: F): F;
function compose<A, T extends any[], R>(
f1: (a: A) => R,
f2: Func<T, A>
): Func<T, R>;
function compose<A, B, T extends any[], R>(
f1: (b: B) => R,
f2: (a: A) => B,
f3: Func<T, A>
): Func<T, R>;
function compose<A, B, C, T extends any[], R>(
f1: (c: C) => R,
f2: (b: B) => C,
f3: (a: A) => B,
f4: Func<T, A>
): Func<T, R>;
function compose<R>(
f1: (a: any) => R,
...funcs: Function[]
): (...args: any[]) => R;
function compose<R>(...funcs: Function[]): (...args: any[]) => R;
type Func<T extends any[], R> = (...a: T) => R;Usage Examples:
import { compose, applyMiddleware } from "redux";
// Compose multiple enhancers
const enhancer = compose(
applyMiddleware(thunk, logger),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
const store = createStore(rootReducer, enhancer);
// Compose functions (general use)
const addOne = (x: number) => x + 1;
const multiplyByTwo = (x: number) => x * 2;
const addThenMultiply = compose(multiplyByTwo, addOne);
console.log(addThenMultiply(3)); // (3 + 1) * 2 = 8
// Compose multiple transformation functions
const trim = (str: string) => str.trim();
const toLowerCase = (str: string) => str.toLowerCase();
const removeSpaces = (str: string) => str.replace(/\s+/g, "");
const normalizeString = compose(removeSpaces, toLowerCase, trim);
console.log(normalizeString(" Hello World ")); // "helloworld"The interface that Redux middleware must implement.
/**
* A middleware is a higher-order function that composes a dispatch function
* to return a new dispatch function
* @template DispatchExt - Extra Dispatch signature added by this middleware
* @template S - The type of the state supported by this middleware
* @template D - The type of Dispatch of the store where this middleware is installed
*/
interface Middleware<
_DispatchExt = {},
S = any,
D extends Dispatch = Dispatch
> {
(api: MiddlewareAPI<D, S>): (
next: (action: unknown) => unknown
) => (action: unknown) => unknown;
}
/**
* The API object passed to middleware providing access to dispatch and getState
* @template D - The dispatch function type
* @template S - The state type
*/
interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
dispatch: D;
getState(): S;
}Usage Examples:
// Logger middleware
const loggerMiddleware: Middleware = (store) => (next) => (action) => {
console.log("Dispatching:", action);
console.log("Previous state:", store.getState());
const result = next(action);
console.log("Next state:", store.getState());
return result;
};
// Async middleware (like redux-thunk)
const thunkMiddleware: Middleware = (store) => (next) => (action) => {
if (typeof action === "function") {
return action(store.dispatch, store.getState);
}
return next(action);
};
// Error handling middleware
const crashReporterMiddleware: Middleware = (store) => (next) => (action) => {
try {
return next(action);
} catch (err) {
console.error("Caught an exception!", err);
// Report to crash reporting service
throw err;
}
};
// Performance monitoring middleware
const performanceMiddleware: Middleware = (store) => (next) => (action) => {
const start = performance.now();
const result = next(action);
const end = performance.now();
console.log(`Action ${action.type} took ${end - start} milliseconds`);
return result;
};Middleware that only processes certain types of actions:
const apiMiddleware: Middleware = (store) => (next) => (action) => {
// Only handle actions with 'api' meta property
if (!action.meta || !action.meta.api) {
return next(action);
}
const { url, method, types } = action.meta.api;
const [requestType, successType, failureType] = types;
// Dispatch request action
next({ type: requestType });
// Make API call
return fetch(url, { method })
.then(response => response.json())
.then(data => next({ type: successType, payload: data }))
.catch(error => next({ type: failureType, payload: error.message }));
};Middleware that behaves differently based on current state:
const authMiddleware: Middleware = (store) => (next) => (action) => {
const state = store.getState();
// Redirect to login if user is not authenticated
if (!state.auth.isAuthenticated && action.type !== "LOGIN") {
console.warn("User not authenticated, redirecting to login");
return next({ type: "REDIRECT_TO_LOGIN" });
}
return next(action);
};Creating middleware that combines multiple behaviors:
const createCompositeMiddleware = (...middlewares: Middleware[]) => {
return (store: MiddlewareAPI) => (next: Dispatch) => {
// Compose all middleware functions
const chain = middlewares.map(middleware => middleware(store));
return compose(...chain)(next);
};
};
// Use composite middleware
const compositeMiddleware = createCompositeMiddleware(
loggerMiddleware,
performanceMiddleware,
crashReporterMiddleware
);Creating fully typed middleware with TypeScript:
interface AppState {
counter: number;
user: User | null;
}
interface AppAction {
type: string;
payload?: any;
}
const typedMiddleware: Middleware<{}, AppState, Dispatch<AppAction>> =
(store) => (next) => (action) => {
// Full type safety for state and actions
const currentState: AppState = store.getState();
if (action.type === "INCREMENT" && currentState.counter >= 100) {
console.warn("Counter limit reached");
return next({ type: "COUNTER_LIMIT_REACHED" });
}
return next(action);
};The type definition for store enhancers.
/**
* A store enhancer is a higher-order function that composes a store creator
* to return a new, enhanced store creator
* @template Ext - Store extension that is mixed into the Store type
* @template StateExt - State extension that is mixed into the state type
*/
type StoreEnhancer<Ext extends {} = {}, StateExt extends {} = {}> = <
NextExt extends {},
NextStateExt extends {}
>(
next: StoreEnhancerStoreCreator<NextExt, NextStateExt>
) => StoreEnhancerStoreCreator<NextExt & Ext, NextStateExt & StateExt>;
type StoreEnhancerStoreCreator<
Ext extends {} = {},
StateExt extends {} = {}
> = <S, A extends Action, PreloadedState>(
reducer: Reducer<S, A, PreloadedState>,
preloadedState?: PreloadedState | undefined
) => Store<S, A, StateExt> & Ext;Creating custom store enhancers for advanced functionality:
// DevTools enhancer
const devToolsEnhancer: StoreEnhancer = (createStore) => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState);
if (window.__REDUX_DEVTOOLS_EXTENSION__) {
return {
...store,
// Add devtools capabilities
};
}
return store;
};
// Persistence enhancer
const persistenceEnhancer: StoreEnhancer = (createStore) => (reducer, preloadedState) => {
// Load persisted state
const persistedState = localStorage.getItem("redux-state");
const initialState = persistedState ? JSON.parse(persistedState) : preloadedState;
const store = createStore(reducer, initialState);
// Save state on every change
store.subscribe(() => {
localStorage.setItem("redux-state", JSON.stringify(store.getState()));
});
return store;
};
// Combine enhancers
const rootEnhancer = compose(
applyMiddleware(thunk, logger),
devToolsEnhancer,
persistenceEnhancer
);Install with Tessl CLI
npx tessl i tessl/npm-redux