Redux Thunk Middleware
Redux Thunk is a middleware for Redux that enables writing action creators that return functions (thunks) instead of plain action objects. These thunk functions receive the `dispatch` and `getState` methods of the Redux store, allowing for asynchronous logic, conditional dispatching, and interaction with the store's current state. The current stable version is 3.1.0, released in conjunction with major updates to the entire Redux ecosystem, including Redux Toolkit 2.0, Redux core 5.0, and React-Redux 9.0. Its release cadence is closely tied to the broader Redux project releases. A key differentiator is its simplicity and official endorsement as the standard approach for handling async logic in Redux, often being included by default in `configureStore` from Redux Toolkit. It also supports injecting custom arguments, facilitating dependency injection patterns for services or APIs.
Common errors
-
TypeError: (0 , redux_thunk__WEBPACK_IMPORTED_MODULE_0__.default) is not a function
cause Attempting to import `redux-thunk` as a default export in v3.x (e.g., `import thunk from 'redux-thunk';`) when it now uses named exports.fixChange your import statement to `import { thunk } from 'redux-thunk';`. -
TypeError: thunk is not a function
cause This error can occur in CommonJS environments or with incorrect tooling if you are trying to use `require('redux-thunk')` directly with `applyMiddleware(thunk)` in v3.x.fixFor v3.x, use `const { thunk } = require('redux-thunk');`. For v2.x, ensure you are using `const thunk = require('redux-thunk').default;`. -
TS2345: Argument of type 'ThunkAction<void, any, unknown, AnyAction>' is not assignable to parameter of type 'AnyAction'.
cause This TypeScript error typically arises when attempting to `dispatch` a thunk action without correctly typing your `dispatch` function to accept `ThunkAction` types.fixCast your `store.dispatch` to `ThunkDispatch` or ensure your `dispatch` parameter in hooks is correctly typed (e.g., `const dispatch = useDispatch<AppDispatch>();` where `AppDispatch` includes `ThunkDispatch`). -
TypeError: Cannot read properties of undefined (reading 'api')
cause This happens when a thunk function expects an `extraArgument` (e.g., `api`) but `redux-thunk` was not configured with one, or the `extraArgument` was not provided as an object when multiple values are expected.fixConfigure the `thunk` middleware with `extraArgument` via `applyMiddleware(thunk.withExtraArgument(myApi))` for manual setup, or within `configureStore`'s `middleware` option: `thunk: { extraArgument: myApi }`.
Warnings
- breaking Redux Thunk v3.0.0 and newer versions changed from a default export to a named export. Imports using `import thunk from 'redux-thunk'` or `const thunk = require('redux-thunk').default` will fail.
- breaking Redux Thunk v3.0.0 introduced significant changes to its packaging for improved ESM/CJS compatibility, aligning with Node.js's `type: "module"` approach. This may affect build tools or environments with strict module resolution.
- gotcha Redux Thunk v3.x has a peer dependency on Redux v5.0.0 or higher. Using it with older Redux versions (e.g., v4.x) might lead to runtime issues or TypeScript incompatibilities.
- deprecated In Redux Thunk v2.0.0, the CommonJS `require('redux-thunk')` without `.default` was deprecated, and required `require('redux-thunk').default` to get the middleware. This pattern is entirely obsolete in v3.x due to the named export change.
- gotcha If you are using Redux Toolkit, `redux-thunk` is already included by default in `configureStore`. Manually applying it via `applyMiddleware` with `configureStore` can lead to duplicate middleware or unexpected behavior.
Install
-
npm install redux-thunk -
yarn add redux-thunk -
pnpm add redux-thunk
Imports
- thunk
import thunk from 'redux-thunk';
import { thunk } from 'redux-thunk'; - thunk (CommonJS)
const thunk = require('redux-thunk');const { thunk } = require('redux-thunk'); - ThunkAction
import type { ThunkAction } from 'redux-thunk'; - ThunkDispatch
import type { ThunkDispatch } from 'redux-thunk';
Quickstart
import { createStore, applyMiddleware, AnyAction } from 'redux';
import { thunk, ThunkAction, ThunkDispatch } from 'redux-thunk';
interface RootState {
count: number;
}
const initialState: RootState = { count: 0 };
function rootReducer(state: RootState = initialState, action: AnyAction): RootState {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
default:
return state;
}
}
// Apply the thunk middleware
const store = createStore(rootReducer, applyMiddleware(thunk));
// Define a simple asynchronous thunk action
const incrementAsync = (amount: number): ThunkAction<void, RootState, unknown, AnyAction> =>
(dispatch: ThunkDispatch<RootState, unknown, AnyAction>, getState) => {
console.log(`Current count before async: ${getState().count}`);
setTimeout(() => {
dispatch({ type: 'INCREMENT', payload: amount });
console.log(`Current count after async: ${getState().count}`);
}, 1000);
};
console.log('Initial state:', store.getState());
(store.dispatch as ThunkDispatch<RootState, unknown, AnyAction>)(incrementAsync(1));
console.log('State after dispatching async thunk (will update later):', store.getState());