Install & Compatibility
Where this runs
tested against v? · npm install
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslnode 18–226 runs
build_error
glibcnode 18–226 runs
build_error
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
temporal
✓ import { temporal } from 'zundo';
✗ import temporal from 'zundo'; // Not a default export
The `temporal` function is a named export, serving as the primary middleware for Zustand stores.
TemporalState
✓ import type { TemporalState } from 'zundo';
Used for TypeScript to define the shape of the state managed by the temporal middleware, especially when accessing history details.
CommonJS require
✓ const { temporal } = require('zundo');
✗ const temporal = require('zundo'); // Requires destructuring
Since v2.0.2, zundo correctly supports both ESM and CommonJS. Ensure proper destructuring for CJS imports.
This example demonstrates how to create a Zustand store with `temporal` middleware, access its undo/redo capabilities, and reactively display historical state counts in a React component.
import { create } from 'zustand';
import { temporal } from 'zundo';
import { useStoreWithEqualityFn } from 'zustand/traditional';
import type { TemporalState } from 'zundo';
interface StoreState {
bears: number;
increasePopulation: () => void;
removeAllBears: () => void;
}
const useStoreWithUndo = create<StoreState>()(
temporal((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
})),
);
// To access temporal functions and reactive temporal state
interface MyTemporalState extends StoreState {
temporal: TemporalState<StoreState>;
}
const useTemporalStore = () => useStoreWithEqualityFn(useStoreWithUndo.temporal.getState);
const App = () => {
const { bears, increasePopulation, removeAllBears } = useStoreWithUndo();
const { undo, redo, clear, pastStates, futureStates } = useTemporalStore();
return (
<>
<h1>Bears: {bears}</h1>
<button onClick={increasePopulation}>Increase</button>
<button onClick={removeAllBears}>Remove All</button>
<button onClick={() => undo()} disabled={pastStates.length === 0}>Undo</button>
<button onClick={() => redo()} disabled={futureStates.length === 0}>Redo</button>
<button onClick={() => clear()}>Clear History</button>
<p>Past states count: {pastStates.length}</p>
<p>Future states count: {futureStates.length}</p>
</>
);
};
export default App;
Debug
Known issues
breakingVersion 2.0.0 was a complete rewrite, introducing significant API changes and a new architecture. Users migrating from v1 will need to update their store definitions and temporal API calls.fixRefer to the v2 migration guide in the zundo GitHub repository for detailed steps on updating API usage.
affects: ^2.0.0
breakingVersion 2.0.1 temporarily changed the default module output from CommonJS (CJS) to ESM, which could break CJS-only environments. This was quickly rectified in v2.0.2.fixEnsure you are using zundo v2.0.2 or higher for proper CJS/ESM dual support, or configure your bundler to handle ESM if explicitly targeting it.
affects: 2.0.1
breakingIn v2.1.0, a bug fix for complex cases of `zundo` and an update to `handleSet` arguments (including `currentState` and `deltaState`) changed previously buggy behavior. If your application relied on the prior buggy behavior, this constitutes a breaking change.fixReview any custom `handleSet` logic after upgrading to v2.1.0 to ensure it aligns with the corrected behavior, particularly if you were indirectly relying on the bug.
affects: ^2.1.0
gotchaWhen accessing properties like `pastStates` or `futureStates` directly from `useStoreWithUndo.temporal.getState()`, these properties are not reactive in React components. Changes will not trigger re-renders.fixFor reactive access to temporal properties, create a dedicated selector hook (e.g., `useTemporalStore` in the quickstart example) using `useStoreWithEqualityFn` from `zustand/traditional` or explicitly subscribe within your component.
affects: >=2.0.0
gotchaWith Zustand v5, the `setState` type became stricter. While zundo v2.3.0 supports these stricter types, applications upgrading to Zustand v5 might encounter TypeScript errors in their `set` calls if they previously used less strict types.fixEnsure your `set` calls within the Zustand store adhere to the stricter `setState` types introduced in Zustand v5. Consult the Zustand v5 migration guide for details on `setState` type changes.
affects: >=2.3.0 (when used with Zustand v5)
Errors
Common errors & fixes
TypeError: Cannot read properties of undefined (reading 'temporal')
Attempting to access `temporal` functions (e.g., `useStore.temporal.getState()`) before the middleware has been correctly applied or if the store itself is not initialized.
fixEnsure `temporal` middleware is correctly wrapped around your `create` function: `create<StoreState>()(temporal((set) => ({...})))`. Also, verify that the store is imported and available where `temporal` is being accessed. ModuleNotFoundError: Package path ./dist/esm/index.js is not exported from package ... (see exports field in .../node_modules/zundo/package.json)
This error specifically occurs if using zundo v2.0.1 in a CommonJS environment, as it temporarily broke CJS support by changing the default module output to ESM.
fixUpgrade zundo to v2.0.2 or higher. Version 2.0.2 restored proper dual CJS/ESM support.
Argument of type '(set: StoreSet<StoreState>) => { ... }' is not assignable to parameter of type 'StoreInitializer<StoreState>'
TypeScript error related to `setState` arguments when using zundo with Zustand v5, often due to stricter `setState` types.
fixAdjust your `set` calls within the store to conform to Zustand v5's stricter `setState` types, especially regarding the `replace` flag. Refer to Zustand's migration guide for v5 type changes.
Audit
Dependencies
zustandrequiredCore state management library that zundo extends as middleware.