CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-redux

Predictable state container for JavaScript apps

Pending
Overview
Eval results
Files

middleware.mddocs/

Middleware and Enhancement

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.

Capabilities

Apply Middleware

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));

Function Composition

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"

Middleware Interface

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;
};

Advanced Middleware Patterns

Conditional Middleware

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 with State Dependencies

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);
};

Middleware Composition

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
);

Type-Safe Middleware

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);
  };

Store Enhancement

Store Enhancer Type

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;

Custom Store Enhancers

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

docs

actions.md

index.md

middleware.md

reducer-composition.md

store-management.md

utilities.md

tile.json