Redux Debounced Middleware
Redux-debounced is a middleware for Redux designed to manage the dispatching of fast-paced actions. It enables developers to debounce actions by adding specific metadata to the action object, ensuring that the Redux state is updated only after a defined period of inactivity following the last dispatch of a particular action. This functionality is crucial for optimizing performance in scenarios like search input fields, where continuous user input could otherwise trigger an excessive number of API requests or state changes. The package is currently at version 0.5.0, with its last publish date in April 2018, which suggests it is in a maintenance state with no active development or regular release cadence. Its primary distinction lies in its direct integration within the Redux middleware chain, utilizing Flux Standard Actions (FSA) for configuration, offering an alternative to debouncing at the component level or within action creators.
Common errors
-
Actions are not debouncing as expected; multiple actions of the same type are dispatched rapidly.
cause The `meta.debounce` property is either missing from the action, or its structure is incorrect (e.g., `time` is not a number, or `debounce` is not directly under `meta`).fixEnsure actions adhere to the Flux Standard Action (FSA) pattern with a `meta` object containing a `debounce` object, which itself has a `time` property (e.g., `{ type: 'MY_ACTION', meta: { debounce: { time: 300 } } }`). -
Redux Thunks are not being debounced, or debounced thunks are not executing at all.
cause This typically occurs if the `createDebounce` middleware is applied after `redux-thunk` middleware, or if a unique `key` is not specified within the `meta.debounce` object for the thunk.fixWhen setting up middleware, ensure `createDebounce()` comes before `thunkMiddleware` (e.g., `applyMiddleware(createDebounce(), thunkMiddleware)`). For thunks, always include `thunk.meta = { debounce: { time: 500, key: 'UNIQUE_THUNK_KEY' } };`. -
TypeError: Cannot read properties of undefined (reading 'debounce') in redux-debounced middleware.
cause This error often indicates that an action without a `meta` object or a `meta.debounce` object is being processed, and the middleware attempts to access properties that don't exist.fixVerify that all actions intended to be debounced conform to the expected FSA `meta.debounce` structure. If non-debounced actions are accidentally being processed, ensure your action creators correctly attach the `meta` object only when needed, or defensively check for its existence in custom middleware.
Warnings
- gotcha When using `redux-debounced` with `redux-thunk`, the `createDebounce` middleware must be applied *before* `thunkMiddleware` in the `applyMiddleware` chain. Additionally, thunks require a `meta.debounce.key` property to be explicitly defined, as thunks do not have a standard 'type' property for the middleware to identify them.
- gotcha A 'cancel' action, specified by `meta.debounce.cancel: true`, will terminate within the `redux-debounced` middleware. It will not propagate further down the middleware chain, appear in Redux DevTools, or trigger any other side effects from subsequent middleware or reducers. This means you cannot 'piggyback' a cancel on another action that is expected to have further effects.
- deprecated The `redux-debounced` package has not been updated since April 2018. While it may still function with older Redux setups, its lack of recent maintenance means it may not be compatible with newer Redux Toolkit patterns or the latest versions of React and Node.js without potential issues.
Install
-
npm install redux-debounced -
yarn add redux-debounced -
pnpm add redux-debounced
Imports
- createDebounce
import { createDebounce } from 'redux-debounced';import createDebounce from 'redux-debounced';
- Middleware
import { Middleware } from 'redux'; - createStore, applyMiddleware
const createStore = require('redux').createStore;import { createStore, applyMiddleware } from 'redux';
Quickstart
import { createStore, applyMiddleware, combineReducers } from 'redux';
import type { Action, Middleware } from 'redux';
import createDebounce from 'redux-debounced';
import thunkMiddleware from 'redux-thunk';
// Minimal reducer for example
interface AppState { searchKey: string; };
const initialState: AppState = { searchKey: '' };
const rootReducer = (state: AppState = initialState, action: Action): AppState => {
switch (action.type) {
case 'TRACK_CUSTOMER_SEARCH':
console.log('Reducer received TRACK_CUSTOMER_SEARCH with key:', (action as any).key);
return { ...state, searchKey: (action as any).key };
default:
return state;
}
};
// Action creator for a debounced thunk
interface DebouncedThunkAction extends Action {
meta?: { debounce?: { time: number; key: string; } };
(dispatch: Function, getState: Function): void;
}
export function trackCustomerSearch(key: string): DebouncedThunkAction {
const thunk = ((dispatch: Function) => {
console.log(`Simulating API call for key: ${key}`);
dispatch({ type: 'TRACK_CUSTOMER_SEARCH', key });
}) as DebouncedThunkAction;
thunk.meta = {
debounce: {
time: 2500, // Debounce for 2.5 seconds
key: 'TRACK_CUSTOMER_SEARCH' // Must specify a key for thunks
}
};
return thunk;
}
// Create the Redux store with middleware
const store = createStore(
rootReducer,
applyMiddleware(createDebounce() as Middleware, thunkMiddleware)
);
// Dispatch debounced actions
console.log('Dispatching search 1 (will be cancelled)');
store.dispatch(trackCustomerSearch('apple'));
setTimeout(() => {
console.log('Dispatching search 2 (will trigger after 2.5s from now)');
store.dispatch(trackCustomerSearch('apricot'));
}, 500);
setTimeout(() => {
console.log('Dispatching search 3 (will trigger after 2.5s from now, cancelling previous)');
store.dispatch(trackCustomerSearch('orange'));
}, 1000);