{"id":17126,"library":"zundo","title":"Zundo: Undo/Redo Middleware for Zustand","description":"Zundo is a lightweight (under 700 bytes) undo/redo middleware designed for Zustand, enabling robust time-travel capabilities in JavaScript and TypeScript applications. Currently at version 2.3.0, it is actively maintained and frequently updated to ensure compatibility with new Zustand versions, including both v4 and v5. Zundo differentiates itself through its minimal bundle size, high flexibility offered by various optional middleware configurations for performance optimization, and an unopinionated, extensible API. It integrates seamlessly with existing Zustand projects, working effectively with multiple stores within a single application to provide undo/redo functionality without significant overhead. Zundo facilitates managing complex state changes by providing simple yet powerful history management.","status":"active","version":"2.3.0","language":"javascript","source_language":"en","source_url":"https://github.com/charkour/zundo","tags":["javascript","undo","redo","history","middleware","zustand","react","typescript"],"install":[{"cmd":"npm install zundo","lang":"bash","label":"npm"},{"cmd":"yarn add zundo","lang":"bash","label":"yarn"},{"cmd":"pnpm add zundo","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Core state management library that zundo extends as middleware.","package":"zustand","optional":false}],"imports":[{"note":"The `temporal` function is a named export, serving as the primary middleware for Zustand stores.","wrong":"import temporal from 'zundo'; // Not a default export","symbol":"temporal","correct":"import { temporal } from 'zundo';"},{"note":"Used for TypeScript to define the shape of the state managed by the temporal middleware, especially when accessing history details.","symbol":"TemporalState","correct":"import type { TemporalState } from 'zundo';"},{"note":"Since v2.0.2, zundo correctly supports both ESM and CommonJS. Ensure proper destructuring for CJS imports.","wrong":"const temporal = require('zundo'); // Requires destructuring","symbol":"CommonJS require","correct":"const { temporal } = require('zundo');"}],"quickstart":{"code":"import { create } from 'zustand';\nimport { temporal } from 'zundo';\nimport { useStoreWithEqualityFn } from 'zustand/traditional';\nimport type { TemporalState } from 'zundo';\n\ninterface StoreState {\n  bears: number;\n  increasePopulation: () => void;\n  removeAllBears: () => void;\n}\n\nconst useStoreWithUndo = create<StoreState>()(\n  temporal((set) => ({\n    bears: 0,\n    increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),\n    removeAllBears: () => set({ bears: 0 }),\n  })),\n);\n\n// To access temporal functions and reactive temporal state\ninterface MyTemporalState extends StoreState {\n  temporal: TemporalState<StoreState>;\n}\nconst useTemporalStore = () => useStoreWithEqualityFn(useStoreWithUndo.temporal.getState);\n\nconst App = () => {\n  const { bears, increasePopulation, removeAllBears } = useStoreWithUndo();\n  const { undo, redo, clear, pastStates, futureStates } = useTemporalStore();\n\n  return (\n    <>\n      <h1>Bears: {bears}</h1>\n      <button onClick={increasePopulation}>Increase</button>\n      <button onClick={removeAllBears}>Remove All</button>\n      <button onClick={() => undo()} disabled={pastStates.length === 0}>Undo</button>\n      <button onClick={() => redo()} disabled={futureStates.length === 0}>Redo</button>\n      <button onClick={() => clear()}>Clear History</button>\n      <p>Past states count: {pastStates.length}</p>\n      <p>Future states count: {futureStates.length}</p>\n    </>\n  );\n};\n\nexport default App;\n","lang":"typescript","description":"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."},"warnings":[{"fix":"Refer to the v2 migration guide in the zundo GitHub repository for detailed steps on updating API usage.","message":"Version 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.","severity":"breaking","affected_versions":"^2.0.0"},{"fix":"Ensure 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.","message":"Version 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.","severity":"breaking","affected_versions":"2.0.1"},{"fix":"Review 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.","message":"In 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.","severity":"breaking","affected_versions":"^2.1.0"},{"fix":"For 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.","message":"When 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.","severity":"gotcha","affected_versions":">=2.0.0"},{"fix":"Ensure 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.","message":"With 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.","severity":"gotcha","affected_versions":">=2.3.0 (when used with Zustand v5)"}],"env_vars":null,"last_verified":"2026-04-22T00:00:00.000Z","next_check":"2026-07-21T00:00:00.000Z","problems":[{"fix":"Ensure `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.","cause":"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.","error":"TypeError: Cannot read properties of undefined (reading 'temporal')"},{"fix":"Upgrade zundo to v2.0.2 or higher. Version 2.0.2 restored proper dual CJS/ESM support.","cause":"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.","error":"ModuleNotFoundError: Package path ./dist/esm/index.js is not exported from package ... (see exports field in .../node_modules/zundo/package.json)"},{"fix":"Adjust 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.","cause":"TypeScript error related to `setState` arguments when using zundo with Zustand v5, often due to stricter `setState` types.","error":"Argument of type '(set: StoreSet<StoreState>) => { ... }' is not assignable to parameter of type 'StoreInitializer<StoreState>'"}],"ecosystem":"npm","meta_description":null}