{"id":13880711,"url":"https://github.com/charkour/zundo","last_synced_at":"2025-05-14T07:08:27.962Z","repository":{"id":37390182,"uuid":"353865159","full_name":"charkour/zundo","owner":"charkour","description":"🍜 undo/redo middleware for zustand. \u003c700 bytes","archived":false,"fork":false,"pushed_at":"2025-04-29T15:15:14.000Z","size":2991,"stargazers_count":724,"open_issues_count":16,"forks_count":24,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-29T16:27:47.984Z","etag":null,"topics":["middleware","redo","undo","zustand"],"latest_commit_sha":null,"homepage":"https://codesandbox.io/s/zundo-2dom9","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/charkour.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["charkour"]}},"created_at":"2021-04-02T00:54:29.000Z","updated_at":"2025-04-29T15:15:19.000Z","dependencies_parsed_at":"2023-12-08T07:35:03.147Z","dependency_job_id":"58ffa4a8-d690-4b1b-901e-a4b99c640a06","html_url":"https://github.com/charkour/zundo","commit_stats":{"total_commits":239,"total_committers":7,"mean_commits":"34.142857142857146","dds":"0.10460251046025104","last_synced_commit":"65a1c99d933d3ee6687b7d3e88efc36e4dee508f"},"previous_names":[],"tags_count":57,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charkour%2Fzundo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charkour%2Fzundo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charkour%2Fzundo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/charkour%2Fzundo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/charkour","download_url":"https://codeload.github.com/charkour/zundo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254092776,"owners_count":22013290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["middleware","redo","undo","zustand"],"created_at":"2024-08-06T08:03:24.979Z","updated_at":"2025-05-14T07:08:27.946Z","avatar_url":"https://github.com/charkour.png","language":"TypeScript","readme":"# 🍜 Zundo\n\nenable time-travel in your apps. undo/redo middleware for [zustand](https://github.com/pmndrs/zustand). built with zustand. \u003c700 B\n\n![gif displaying undo feature](https://github.com/charkour/zundo/raw/v0.2.0/zundo.gif)\n\n[![Build Size](https://img.shields.io/bundlephobia/minzip/zundo?label=bundle%20size\u0026style=flat\u0026colorA=000000\u0026colorB=000000)](https://bundlephobia.com/result?p=zundo)\n[![Version](https://img.shields.io/npm/v/zundo?style=flat\u0026colorA=000000\u0026colorB=000000)](https://www.npmjs.com/package/zundo)\n[![Downloads](https://img.shields.io/npm/dt/zundo?style=flat\u0026colorA=000000\u0026colorB=000000)](https://www.npmjs.com/package/zundo)\n\nTry a live [demo](https://codesandbox.io/s/currying-flower-2dom9?file=/src/App.tsx)\n\n## Install\n\n```sh\nnpm i zustand zundo\n```\n\n\u003e zustand v4.2.0+ or v5 is required for TS usage. v4.0.0 or higher is required for JS usage.\n\u003e Node 16 or higher is required.\n\n## Background\n\n- Solves the issue of managing state in complex user applications\n- \"It Just Works\" mentality\n- Small and fast\n- Provides simple middleware to add undo/redo capabilities\n- Leverages zustand for state management\n- Works with multiple stores in the same app\n- Has an unopinionated and extensible API\n\n\u003cdiv style=\"width: 100%; display: flex;\"\u003e\n\u003cimg src=\"https://github.com/charkour/zundo/blob/main/zundo-mascot.png\" style=\"margin: auto;\" alt=\"Bear wearing a button up shirt textured with blue recycle symbols eating a bowl of noodles with chopsticks.\" width=300 /\u003e\n\u003c/div\u003e\n\n## First create a vanilla store with `temporal` middleware\n\nThis returns the familiar store accessible by a hook! But now your store also tracks past states.\n\n```tsx\nimport { create } from 'zustand';\nimport { temporal } from 'zundo';\n\n// Define the type of your store state (typescript)\ninterface StoreState {\n  bears: number;\n  increasePopulation: () =\u003e void;\n  removeAllBears: () =\u003e void;\n}\n\n// Use `temporal` middleware to create a store with undo/redo capabilities\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal((set) =\u003e ({\n    bears: 0,\n    increasePopulation: () =\u003e set((state) =\u003e ({ bears: state.bears + 1 })),\n    removeAllBears: () =\u003e set({ bears: 0 }),\n  })),\n);\n```\n\n## Then access `temporal` functions and properties of your store\n\nYour zustand store will now have an attached `temporal` object that provides access to useful time-travel utilities, including `undo`, `redo`, and `clear`!\n\n```tsx\nconst App = () =\u003e {\n  const { bears, increasePopulation, removeAllBears } = useStoreWithUndo();\n  // See API section for temporal.getState() for all functions and\n  // properties provided by `temporal`, but note that properties, such as `pastStates` and `futureStates`, are not reactive when accessed directly from the store.\n  const { undo, redo, clear } = useStoreWithUndo.temporal.getState();\n\n  return (\n    \u003c\u003e\n      bears: {bears}\n      \u003cbutton onClick={() =\u003e increasePopulation}\u003eincrease\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e removeAllBears}\u003eremove\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e undo()}\u003eundo\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e redo()}\u003eredo\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e clear()}\u003eclear\u003c/button\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n## For reactive changes to member properties of the `temporal` object, optionally convert to a React store hook\n\nIn React, to subscribe components or custom hooks to member properties of the `temporal` object (like the array of `pastStates` or `currentStates`), you can create a `useTemporalStore` hook.\n\n```tsx\nimport { useStoreWithEqualityFn } from 'zustand/traditional';\nimport type { TemporalState } from 'zundo';\n\nfunction useTemporalStore(): TemporalState\u003cMyState\u003e;\nfunction useTemporalStore\u003cT\u003e(selector: (state: TemporalState\u003cMyState\u003e) =\u003e T): T;\nfunction useTemporalStore\u003cT\u003e(\n  selector: (state: TemporalState\u003cMyState\u003e) =\u003e T,\n  equality: (a: T, b: T) =\u003e boolean,\n): T;\nfunction useTemporalStore\u003cT\u003e(\n  selector?: (state: TemporalState\u003cMyState\u003e) =\u003e T,\n  equality?: (a: T, b: T) =\u003e boolean,\n) {\n  return useStoreWithEqualityFn(useStoreWithUndo.temporal, selector!, equality);\n}\n\nconst App = () =\u003e {\n  const { bears, increasePopulation, removeAllBears } = useStoreWithUndo();\n  // changes to pastStates and futureStates will now trigger a reactive component rerender\n  const { undo, redo, clear, pastStates, futureStates } = useTemporalStore(\n    (state) =\u003e state,\n  );\n\n  return (\n    \u003c\u003e\n      \u003cp\u003e bears: {bears}\u003c/p\u003e\n      \u003cp\u003e pastStates: {JSON.stringify(pastStates)}\u003c/p\u003e\n      \u003cp\u003e futureStates: {JSON.stringify(futureStates)}\u003c/p\u003e\n      \u003cbutton onClick={() =\u003e increasePopulation}\u003eincrease\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e removeAllBears}\u003eremove\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e undo()}\u003eundo\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e redo()}\u003eredo\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e clear()}\u003eclear\u003c/button\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n## API\n\n### The Middleware\n\n`(config: StateCreator, options?: ZundoOptions) =\u003e StateCreator`\n\n`zundo` has one export: `temporal`. It is used as middleware for `create` from zustand. The `config` parameter is your store created by zustand. The second `options` param is optional and has the following API.\n\n### Bear's eye view\n\n```tsx\nexport interface ZundoOptions\u003cTState, PartialTState = TState\u003e {\n  partialize?: (state: TState) =\u003e PartialTState;\n  limit?: number;\n  equality?: (pastState: PartialTState, currentState: PartialTState) =\u003e boolean;\n  diff?: (\n    pastState: Partial\u003cPartialTState\u003e,\n    currentState: Partial\u003cPartialTState\u003e,\n  ) =\u003e Partial\u003cPartialTState\u003e | null;\n  onSave?: (pastState: TState, currentState: TState) =\u003e void;\n  handleSet?: (\n    handleSet: StoreApi\u003cTState\u003e['setState'],\n  ) =\u003e StoreApi\u003cTState\u003e['setState'];\n  pastStates?: Partial\u003cPartialTState\u003e[];\n  futureStates?: Partial\u003cPartialTState\u003e[];\n  wrapTemporal?: (\n    storeInitializer: StateCreator\u003c\n      _TemporalState\u003cTState\u003e,\n      [StoreMutatorIdentifier, unknown][],\n      []\n    \u003e,\n  ) =\u003e StateCreator\u003c\n    _TemporalState\u003cTState\u003e,\n    [StoreMutatorIdentifier, unknown][],\n    [StoreMutatorIdentifier, unknown][]\n  \u003e;\n}\n```\n\n### Exclude fields from being tracked in history\n\n`partialize?: (state: TState) =\u003e PartialTState`\n\nUse the `partialize` option to omit or include specific fields. Pass a callback that returns the desired fields. This can also be used to exclude fields. By default, the entire state object is tracked.\n\n```tsx\n// Only field1 and field2 will be tracked\nconst useStoreWithUndoA = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    {\n      partialize: (state) =\u003e {\n        const { field1, field2, ...rest } = state;\n        return { field1, field2 };\n      },\n    },\n  ),\n);\n\n// Everything besides field1 and field2 will be tracked\nconst useStoreWithUndoB = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    {\n      partialize: (state) =\u003e {\n        const { field1, field2, ...rest } = state;\n        return rest;\n      },\n    },\n  ),\n);\n```\n\n#### `useTemporalStore` with `partialize`\n\nIf converting temporal store to a React Store Hook with typescript, be sure to define the type of your partialized state\n\n```tsx\ninterface StoreState {\n  bears: number;\n  untrackedStateField: number;\n}\n\ntype PartializedStoreState = Pick\u003cStoreState, 'bears'\u003e;\n\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      bears: 0,\n      untrackedStateField: 0,\n    }),\n    {\n      partialize: (state) =\u003e {\n        const { bears } = state;\n        return { bears };\n      },\n    },\n  ),\n);\n\nconst useTemporalStore = \u003cT,\u003e(\n  // Use partalized StoreState type as the generic here\n  selector: (state: TemporalState\u003cPartializedStoreState\u003e) =\u003e T,\n) =\u003e useStore(useStoreWithUndo.temporal, selector);\n```\n\n### Limit number of historical states stored\n\n`limit?: number`\n\nFor performance reasons, you may want to limit the number of previous and future states stored in history. Setting `limit` will limit the number of previous and future states stored in the `temporal` store. When the limit is reached, the oldest state is dropped. By default, no limit is set.\n\n```tsx\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    { limit: 100 },\n  ),\n);\n```\n\n### Prevent unchanged states from getting stored in history\n\n`equality?: (pastState: PartialTState, currentState: PartialTState) =\u003e boolean`\n\nBy default, a state snapshot is stored in `temporal` history when _any_ `zustand` state setter is called—even if no value in your `zustand` store has changed.\n\nIf all of your `zustand` state setters modify state in a way that you want tracked in history, this default is sufficient.\n\nHowever, for more precise control over when a state snapshot is stored in `zundo` history, you can provide an `equality` function.\n\nYou can write your own equality function or use something like [`fast-equals`](https://github.com/planttheidea/fast-equals), [`fast-deep-equal`](https://github.com/epoberezkin/fast-deep-equal), [`zustand/shallow`](https://github.com/pmndrs/zustand/blob/main/src/shallow.ts), [`lodash.isequal`](https://www.npmjs.com/package/lodash.isequal), or [`underscore.isEqual`](https://github.com/jashkenas/underscore/blob/master/modules/isEqual.js).\n\n#### Example with deep equality\n\n```tsx\nimport isDeepEqual from 'fast-deep-equal';\n\n// Use a deep equality function to only store history when currentState has changed\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    // a state snapshot will only be stored in history when currentState is not deep-equal to pastState\n    // Note: this can also be more concisely written as {equality: isDeepEqual}\n    {\n      equality: (pastState, currentState) =\u003e\n        isDeepEqual(pastState, currentState),\n    },\n  ),\n);\n```\n\n#### Example with shallow equality\n\nIf your state or specific application does not require deep equality (for example, if you're only using non-nested primitives), you may for performance reasons choose to use a shallow equality fn that does not do deep comparison.\n\n```tsx\nimport shallow from 'zustand/shallow';\n\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    // a state snapshot will only be stored in history when currentState is not deep-equal to pastState\n    // Note: this can also be more concisely written as {equality: shallow}\n    {\n      equality: (pastState, currentState) =\u003e shallow(pastState, currentState),\n    },\n  ),\n);\n```\n\n#### Example with custom equality\n\nYou can also just as easily use custom equality functions for your specific application\n\n```tsx\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    {\n      // Only track history when field1 AND field2 diverge from their pastState\n      // Why would you do this? I don't know! But you can do it!\n      equality: (pastState, currentState) =\u003e\n        pastState.field1 !== currentState.field1 \u0026\u0026\n        pastState.field2 !== currentState.field2,\n    },\n  ),\n);\n```\n\n### Store state delta rather than full object\n\n`diff?: (pastState: Partial\u003cPartialTState\u003e, currentState: Partial\u003cPartialTState\u003e) =\u003e Partial\u003cPartialTState\u003e | null`\n\nFor performance reasons, you may want to store the state delta rather than the complete (potentially partialized) state object. This can be done by passing a `diff` function. The `diff` function should return an object that represents the difference between the past and current state. By default, the full state object is stored.\n\nIf `diff` returns `null`, the state change will not be tracked. This is helpful for a conditionally storing past states or if you have a `doNothing` action that does not change the state.\n\nYou can write your own or use something like [`microdiff`](https://github.com/AsyncBanana/microdiff), [`just-diff`](https://github.com/angus-c/just/tree/master/packages/collection-diff), or [`deep-object-diff`](https://github.com/mattphillips/deep-object-diff).\n\n```tsx\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    {\n      diff: (pastState, currentState) =\u003e {\n        const myDiff = diff(currentState, pastState);\n        const newStateFromDiff = myDiff.reduce(\n          (acc, difference) =\u003e {\n            type Key = keyof typeof currentState;\n            if (difference.type === 'CHANGE') {\n              const pathAsString = difference.path.join('.') as Key;\n              acc[pathAsString] = difference.value;\n            }\n            return acc;\n          },\n          {} as Partial\u003ctypeof currentState\u003e,\n        );\n        return isEmpty(newStateFromDiff) ? null : newStateFromDiff;\n      },\n    },\n  ),\n);\n```\n\n### Callback when temporal store is updated\n\n`onSave?: (pastState: TState, currentState: TState) =\u003e void`\n\nSometimes, you may need to call a function when the temporal store is updated. This can be configured using `onSave` in the options, or by programmatically setting the callback if you need lexical context (see the `TemporalState` API below for more information).\n\n```tsx\nimport { shallow } from 'zustand/shallow';\n\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    { onSave: (state) =\u003e console.log('saved', state) },\n  ),\n);\n```\n\n### Cool-off period\n\n```typescript\n  handleSet?: (handleSet: StoreApi\u003cTState\u003e['setState']) =\u003e (\n    pastState: Parameters\u003cStoreApi\u003cTState\u003e['setState']\u003e[0],\n    // `replace` will likely be deprecated and removed in the future\n    replace: Parameters\u003cStoreApi\u003cTState\u003e['setState']\u003e[1],\n    currentState: PartialTState,\n    deltaState?: Partial\u003cPartialTState\u003e | null,\n) =\u003e void\n```\n\nSometimes multiple state changes might happen in a short amount of time and you only want to store one change in history. To do so, we can utilize the `handleSet` callback to set a timeout to prevent new changes from being stored in history. This can be used with something like [`throttle-debounce`](https://github.com/niksy/throttle-debounce), [`just-throttle`](https://github.com/angus-c/just/tree/master/packages/function-throttle), [`just-debounce-it`](https://github.com/angus-c/just/tree/master/packages/function-debounce), [`lodash.throttle`](https://www.npmjs.com/package/lodash.throttle), or [`lodash.debounce`](https://www.npmjs.com/package/lodash.debounce). This a way to provide middleware to the temporal store's setter function.\n\n```tsx\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    {\n      handleSet: (handleSet) =\u003e\n        throttle\u003ctypeof handleSet\u003e((state) =\u003e {\n          console.info('handleSet called');\n          handleSet(state);\n        }, 1000),\n    },\n  ),\n);\n```\n\n### Initialize temporal store with past and future states\n\n`pastStates?: Partial\u003cPartialTState\u003e[]`\n\n`futureStates?: Partial\u003cPartialTState\u003e[]`\n\nYou can initialize the temporal store with past and future states. This is useful when you want to load a previous state from a database or initialize the store with a default state. By default, the temporal store is initialized with an empty array of past and future states.\n\n\u003e Note: The `pastStates` and `futureStates` do not respect the limit set in the options. If you want to limit the number of past and future states, you must do so manually prior to initializing the store.\n\n```tsx\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    {\n      pastStates: [{ field1: 'value1' }, { field1: 'value2' }],\n      futureStates: [{ field1: 'value3' }, { field1: 'value4' }],\n    },\n  ),\n);\n```\n\n### Wrap temporal store\n\n`wrapTemporal?: (storeInitializer: StateCreator\u003c_TemporalState\u003cTState\u003e, [StoreMutatorIdentifier, unknown][], []\u003e) =\u003e StateCreator\u003c_TemporalState\u003cTState\u003e, [StoreMutatorIdentifier, unknown][], [StoreMutatorIdentifier, unknown][]\u003e`\n\nYou can wrap the temporal store with your own middleware. This is useful if you want to add additional functionality to the temporal store. For example, you can add `persist` middleware to the temporal store to persist the past and future states to local storage.\n\nFor a full list of middleware, see [zustand middleware](https://www.npmjs.com/package/lodash.debounce) and [third-party zustand libraries](https://github.com/pmndrs/zustand#third-party-libraries).\n\n\u003e Note: The `temporal` middleware can be added to the `temporal` store. This way, you could track the history of the history. 🤯\n\n```tsx\nimport { persist } from 'zustand/middleware';\n\nconst useStoreWithUndo = create\u003cStoreState\u003e()(\n  persist(                                      // \u003c-- persist\n    temporal(\n      (set) =\u003e ({\n       // your store fields\n      }),\n      {\n        wrapTemporal: (storeInitializer) =\u003e {\n          persist(storeInitializer, {           // \u003c-- persist \n            name: 'temporal-persist' \n          });\n        },\n      }\n    )\n  )\n);\n```\n\n\u003e In the example above, note that we use `persist` twice. The outer persist is persisting your user facing store, and the inner persist, as part of the temporal options, will persist the temporal store that's created by the middleware. Simply put: there are two zustand stores, so you must persist both.\n\n\n### `useStore.temporal`\n\nWhen using zustand with the `temporal` middleware, a `temporal` object is attached to your vanilla or React-based store. `temporal` is a vanilla zustand store: see [StoreApi\u003cT\u003e from](https://github.com/pmndrs/zustand/blob/f0ff30f7c431f6bf25b3cb439d065a7e61355df4/src/vanilla.ts#L8) zustand for more details.\n\nUse `temporal.getState()` to access to temporal store!\n\n\u003e While `setState`, `subscribe`, and `destroy` exist on `temporal`, you should not need to use them.\n\n### `useStore.temporal.getState()`\n\n`temporal.getState()` returns the `TemporalState` which contains `undo`, `redo`, and other helpful functions and fields.\n\n```tsx\ninterface TemporalState\u003cTState\u003e {\n  pastStates: TState[];\n  futureStates: TState[];\n\n  undo: (steps?: number) =\u003e void;\n  redo: (steps?: number) =\u003e void;\n  clear: () =\u003e void;\n\n  isTracking: boolean;\n  pause: () =\u003e void;\n  resume: () =\u003e void;\n\n  setOnSave: (onSave: onSave\u003cTState\u003e) =\u003e void;\n}\n```\n\n#### **Going back in time**\n\n`pastStates: TState[]`\n\n`pastStates` is an array of previous states. The most recent previous state is at the end of the array. This is the state that will be applied when `undo` is called.\n\n#### **Forward to the future**\n\n`futureStates: TState[]`\n\n`futureStates` is an array of future states. States are added when `undo` is called. The most recent future state is at the end of the array. This is the state that will be applied when `redo` is called. The future states are the \"past past states.\"\n\n#### **Back it up**\n\n`undo: (steps?: number) =\u003e void`\n\n`undo`: call function to apply previous state (if there are previous states). Optionally pass a number of steps to undo to go back multiple state at once.\n\n#### **Take it back now y'all**\n\n`redo: (steps?: number) =\u003e void`\n\n`redo`: call function to apply future state (if there are future states). Future states are \"previous previous states.\" Optionally pass a number of steps to redo go forward multiple states at once.\n\n#### **Remove all knowledge of time**\n\n`clear: () =\u003e void`\n\n`clear`: call function to remove all stored states from your undo store. Sets `pastStates` and `futureStates` to arrays with length of 0. _Warning:_ clearing cannot be undone.\n\n**Dispatching a new state will clear all of the future states.**\n\n#### **Stop and start history**\n\n`isTracking: boolean`\n\n`isTracking`: a stateful flag in the `temporal` store that indicates whether the `temporal` store is tracking state changes or not. Possible values are `true` or `false`. To programmatically pause and resume tracking, use `pause()` and `resume()` explained below.\n\n#### **Pause tracking of history**\n\n`pause: () =\u003e void`\n\n`pause`: call function to pause tracking state changes. This will prevent new states from being stored in history within the temporal store. Sets `isTracking` to `false`.\n\n#### **Resume tracking of history**\n\n`resume: () =\u003e void`\n\n`resume`: call function to resume tracking state changes. This will allow new states to be stored in history within the temporal store. Sets `isTracking` to `true`.\n\n#### **Programmatically add middleware to the setter**\n\n`setOnSave: (onSave: (pastState: State, currentState: State) =\u003e void) =\u003e void`\n\n`setOnSave`: call function to set a callback that will be called when the temporal store is updated. This can be used to call the temporal store setter using values from the lexical context. This is useful when needing to throttle or debounce updates to the temporal store.\n\n## Community\n\n`zundo` is used by several projects and teams including [Alibaba](https://github.com/alibaba/x-render), [Dify.ai](https://github.com/langgenius/dify), [Stability AI](https://github.com/Stability-AI/StableStudio), [Yext](https://github.com/yext/studio), [KaotoIO](https://github.com/KaotoIO/kaoto-ui), and [NutSH.ai](https://github.com/SysCV/nutsh).\n\nIf this library is useful to you, please consider [sponsoring](https://github.com/sponsors/charkour) the project. Thank you!\n\nPRs are welcome! [pnpm](https://pnpm.io/) is used as a package manager. Run `pnpm install` to install local dependencies. Thank you for contributing!\n\n## Examples\n\n- [Basic](https://codesandbox.io/s/currying-flower-2dom9?file=/src/App.tsx)\n- [with lodash.debounce](https://codesandbox.io/s/zundo-handleset-debounce-nq7ml7?file=/src/App.tsx)\n- [with just-debounce-it](https://codesandbox.io/p/sandbox/zundo-forked-9yp7df)\n- [SubscribeWithSelector](https://codesandbox.io/s/zundo-with-subscribe-with-selector-forked-mug69t)\n- [canUndo, canRedo, undoDepth, redoDepth](https://codesandbox.io/s/zundo-canundo-and-undodepth-l6jclx?file=/src/App.tsx:572-731)\n- [with deep equal](https://codesandbox.io/p/sandbox/zundo-deep-equal-qg69lj)\n- [with input](https://stackblitz.com/edit/vitejs-vite-jqngm9?file=src%2FApp.tsx)\n- [with slices pattern](https://codesandbox.io/p/sandbox/pttx6c)\n\n## Migrate from v1 to v2\n\n\u003cdetails\u003e\n\u003csummary\u003eClick to expand\u003c/summary\u003e\n\n## v2.0.0 - Smaller and more flexible\n\nv2.0.0 is a complete rewrite of zundo. It is smaller and more flexible. It also has a smaller bundle size and allows you to opt into specific performance trade-offs. The API has changed slightly. See the [API](#api) section for more details. Below is a summary of the changes as well as steps to migrate from v1 to v2.\n\n### Breaking Changes\n\n#### Middleware Option Changes\n\n- `include` and `exclude` options are now handled by the `partialize` option.\n- `allowUnchanged` option is now handled by the `equality` option. By default, all state changes are tracked. In v1, we bundled `lodash.isequal` to handle equality checks. In v2, you are able to use any function.\n- `historyDepthLimit` option has been renamed to `limit`.\n- `coolOffDurationMs` option is now handled by the `handleSet` option by wrapping the setter function with a throttle or debounce function.\n\n#### Import changes\n\n- The middleware is called `temporal` rather than `undoMiddleware`.\n\n### New Features\n\n#### New Options\n\n- `partialize` option to omit or include specific fields. By default, the entire state object is tracked.\n- `limit` option to limit the number of previous and future states stored in history.\n- `equality` option to use a custom equality function to determine when a state change should be tracked. By default, all state changes are tracked.\n- `diff` option to store state delta rather than full object.\n- `onSave` option to call a function when the temporal store is updated.\n- `handleSet` option to throttle or debounce state changes.\n- `pastStates` and `futureStates` options to initialize the temporal store with past and future states.\n- `wrapTemporal` option to wrap the temporal store with middleware. The `temporal` store is a vanilla zustand store.\n\n#### New `temporal.getState()` API\n\n- `undo`, `redo`, and `clear` functions are now always defined. They can no longer be `undefined`.\n- `undo()` and `redo()` functions now accept an optional `steps` parameter to go back or forward multiple states at once.\n- `isTracking` flag, and `pause`, and `resume` functions are now available on the temporal store.\n- `setOnSave` function is now available on the temporal store to change the `onSave` behavior after the store has been created.\n\n### Migration Steps\n\n1. Update zustand to v4.3.0 or higher\n2. Update zundo to v2.0.0 or higher\n3. Update your store to use the new API\n4. Update imports\n\n```diff\n- import { undoMiddleware } from 'zundo';\n+ import { temporal } from 'zundo';\n```\n\n- If you're using `include` or `exclude`, use the new `partialize` option\n\n```tsx\n// v1.6.0\n// Only field1 and field2 will be tracked\nconst useStoreA = create\u003cStoreState\u003e()(\n  undoMiddleware(\n    set =\u003e ({ ... }),\n    { include: ['field1', 'field2'] }\n  )\n);\n\n// Everything besides field1 and field2 will be tracked\nconst useStoreB = create\u003cStoreState\u003e()(\n  undoMiddleware(\n    set =\u003e ({ ... }),\n    { exclude: ['field1', 'field2'] }\n  )\n);\n\n// v2.0.0\n// Only field1 and field2 will be tracked\nconst useStoreA = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    {\n      partialize: (state) =\u003e {\n        const { field1, field2, ...rest } = state;\n        return { field1, field2 };\n      },\n    },\n  ),\n);\n\n// Everything besides field1 and field2 will be tracked\nconst useStoreB = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    {\n      partialize: (state) =\u003e {\n        const { field1, field2, ...rest } = state;\n        return rest;\n      },\n    },\n  ),\n);\n```\n\n- If you're using `allowUnchanged`, use the new `equality` option\n\n```tsx\n// v1.6.0\n// Use an existing `allowUnchanged` option\nconst useStore = create\u003cStoreState\u003e()(\n  undoMiddleware(\n    set =\u003e ({ ... }),\n    { allowUnchanged: true }\n  )\n);\n\n// v2.0.0\n// Use an existing equality function\nimport { shallow } from 'zustand/shallow'; // or use `lodash.isequal` or any other equality function\n\n// Use an existing equality function\nconst useStoreA = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    { equality: shallow },\n  ),\n);\n```\n\n- If you're using `historyDepthLimit`, use the new `limit` option\n\n```tsx\n// v1.6.0\n// Use an existing `historyDepthLimit` option\nconst useStore = create\u003cStoreState\u003e()(\n  undoMiddleware(\n    set =\u003e ({ ... }),\n    { historyDepthLimit: 100 }\n  )\n);\n\n// v2.0.0\n// Use `limit` option\nconst useStore = create\u003cStoreState\u003e()(\n  temporal(\n    (set) =\u003e ({\n      // your store fields\n    }),\n    { limit: 100 },\n  ),\n);\n```\n\n- If you're using `coolOffDurationMs`, use the new `handleSet` option\n\n```tsx\n// v1.6.0\n// Use an existing `coolOffDurationMs` option\nconst useStore = create\u003cStoreState\u003e()(\n  undoMiddleware(\n    set =\u003e ({ ... }),\n    { coolOfDurationMs: 1000 }\n  )\n);\n\n// v2.0.0\n// Use `handleSet` option\nconst withTemporal = temporal\u003cMyState\u003e(\n  (set) =\u003e ({\n    // your store fields\n  }),\n  {\n    handleSet: (handleSet) =\u003e\n      throttle\u003ctypeof handleSet\u003e((state) =\u003e {\n        console.info('handleSet called');\n        handleSet(state);\n      }, 1000),\n  },\n);\n```\n\n\u003c/details\u003e\n\n## Road Map\n\n- [ ] create nicer API, or a helper hook in react land (useTemporal). or vanilla version of the it\n- [ ] support history branches rather than clearing the future states\n- [ ] track state for multiple stores at once\n\n## Author\n\nCharles Kornoelje ([@\\_charkour](https://twitter.com/_charkour))\n\n## Versioning\n\nView the [releases](https://github.com/charkour/zundo/releases) for the change log. This project follows semantic versioning.\n\n## Illustration Credits\n\nIvo Ilić ([@theivoson](https://twitter.com/theivoson))\n","funding_links":["https://github.com/sponsors/charkour"],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharkour%2Fzundo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcharkour%2Fzundo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharkour%2Fzundo/lists"}