Skip to content

Releases: charkour/zundo

v2.3.0 - zustand v5 support

17 Nov 16:39
Compare
Choose a tag to compare

Release notes

  • This release officially supports zustand v5.
  • zundo will continue to work with zustand v4 and v5

What's Changed

Full Changelog: v2.2.0...v2.3.0

v2.2.0 - TypeScript Bundling Updates

03 Oct 01:42
Compare
Choose a tag to compare

Updates

Nothing much was changed other than updating dependencies, fixing a bug in a test, and adding automated workflows.

What's Changed

Full Changelog: v2.1.0...v2.2.0

v2.1.0 - Update Handle Set arguments

21 Jan 19:28
Compare
Choose a tag to compare

Release notes

  • fixed a bug for complex cases of zundo and update handleSet arguments to include currentState and deltaState (#139) in #149
    • 🚨 Warning: This changes behavior that previously was a bug. If you relied on this behavior, there may be a breaking change.
  • updated peer dependency to test with zustand v4.5

What's Changed

Full Changelog: v2.0.3...v2.1.0

v2.0.3 - TS Fix and Docs

23 Dec 16:16
Compare
Choose a tag to compare

Release notes

  • @pstrassmann fixed a TypeScript bug and spent a lot of time updating the docs! πŸŽ‰

What's Changed

New Contributors

Full Changelog: v2.0.2...v2.0.3

v2.0.2 - Actually support ESM and CJS

23 Dec 15:48
Compare
Choose a tag to compare

Release notes

v2.0.1 attempted to support ESM and CJS, but it instead changed the module support from CJS to ESM which is a potentially breaking change based on the user's JS runtime specification. This change fixes the package.json file to support ESM or CJS, depending on what's required by the user's runtime.

What's Changed

Full Changelog: v2.0.1...v2.0.2

v2.0.1 - CJS (and ESM) and Deno

09 Dec 02:36
Compare
Choose a tag to compare

v2.0.1 Release Notes

  • 🚨 Potentially breaking: this release introduces a potentially breaking change, the default output switched from CJS to ESM. Please use version v2.0.2 to avoid this potentially breaking change.
  • zundo is available via deno.land/x/zundo
  • zundo is available in cjs in addition to esm now

What's Changed

New Contributors

Full Changelog: v2.0.0...v2.0.1

v2.0.0 - Smaller and more flexible πŸŽ‰

16 Sep 21:15
Compare
Choose a tag to compare

v2.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 section for more details. Below is a summary of the changes as well as steps to migrate from v1 to v2.

v2 introduces 8 new middleware options and is only ~800 bytes where v1 was a few KBs.

Community

zundo is used by several projects and teams including Stability AI, Yext, KaotoIO, and NutSH.ai.

If this library is useful to you, please consider sponsoring the project. Thank you!

PRs are welcome! pnpm is used as a package manager. Run pnpm install to install local dependencies. Thank you for contributing!

Breaking Changes

Middleware Option Changes

  • include and exclude options are now handled by the partialize option.
  • 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.
  • historyDepthLimit option has been renamed to limit.
  • coolOffDurationMs option is now handled by the handleSet option by wrapping the setter function with a throttle or debounce function.

Import changes

  • The middleware is called temporal rather than undoMiddleware.

New Features

New Options

  • partialize option to omit or include specific fields. By default, the entire state object is tracked.
  • limit option to limit the number of previous and future states stored in history.
  • equality option to use a custom equality function to determine when a state change should be tracked. By default, all state changes are tracked.
  • diff option to store state delta rather than full object.
  • onSave option to call a function when the temporal store is updated.
  • handleSet option to throttle or debounce state changes.
  • pastStates and futureStates options to initialize the temporal store with past and future states.
  • wrapTemporal option to wrap the temporal store with middleware. The temporal store is a vanilla zustand store.

New temporal.getState() API

  • undo, redo, and clear functions are now always defined. They can no longer be undefined.
  • undo() and redo() functions now accept an optional steps parameter to go back or forward multiple states at once.
  • isTracking flag, and pause, and resume functions are now available on the temporal store.
  • setOnSave function is now available on the temporal store to change the onSave behavior after the store has been created.

Migration Steps

  1. Update zustand to v4.3.0 or higher
  2. Update zundo to v2.0.0 or higher
  3. Update your store to use the new API
  4. Update imports
- import { undoMiddleware } from 'zundo';
+ import { temporal } from 'zundo';
  • If you're using include or exclude, use the new partialize option
// v1.6.0
// Only field1 and field2 will be tracked
const useStoreA = create<StoreState>(
  undoMiddleware(
    set => ({ ... }),
    { include: ['field1', 'field2'] }
  )
);

// Everything besides field1 and field2 will be tracked
const useStoreB = create<StoreState>(
  undoMiddleware(
    set => ({ ... }),
    { exclude: ['field1', 'field2'] }
  )
);

// v2.0.0
// Only field1 and field2 will be tracked
const useStoreA = create<StoreState>(
  temporal(
    (set) => ({
      // your store fields
    }),
    {
      partialize: (state) => {
        const { field1, field2, ...rest } = state;
        return { field1, field2 };
      },
    },
  ),
);

// Everything besides field1 and field2 will be tracked
const useStoreB = create<StoreState>(
  temporal(
    (set) => ({
      // your store fields
    }),
    {
      partialize: (state) => {
        const { field1, field2, ...rest } = state;
        return rest;
      },
    },
  ),
);
  • If you're using allowUnchanged, use the new equality option
// v1.6.0
// Use an existing `allowUnchanged` option
const useStore = create<StoreState>(
  undoMiddleware(
    set => ({ ... }),
    { allowUnchanged: true }
  )
);

// v2.0.0
// Use an existing equality function
import { shallow } from 'zustand/shallow'; // or use `lodash.isequal` or any other equality function

// Use an existing equality function
const useStoreA = create<StoreState>(
  temporal(
    (set) => ({
      // your store fields
    }),
    { equality: shallow },
  ),
);
  • If you're using historyDepthLimit, use the new limit option
// v1.6.0
// Use an existing `historyDepthLimit` option
const useStore = create<StoreState>(
  undoMiddleware(
    set => ({ ... }),
    { historyDepthLimit: 100 }
  )
);

// v2.0.0
// Use `limit` option
const useStore = create<StoreState>(
  temporal(
    (set) => ({
      // your store fields
    }),
    { limit: 100 },
  ),
);
  • If you're using coolOffDurationMs, use the new handleSet option
// v1.6.0
// Use an existing `coolOffDurationMs` option
const useStore = create<StoreState>(
  undoMiddleware(
    set => ({ ... }),
    { coolOfDurationMs: 1000 }
  )
);

// v2.0.0
// Use `handleSet` option
const withTemporal = temporal<MyState>(
  (set) => ({
    // your store fields
  }),
  {
    handleSet: (handleSet) =>
      throttle<typeof handleSet>((state) => {
        console.info('handleSet called');
        handleSet(state);
      }, 1000),
  },
);

What's Changed

New Contributors

Full Changelog: v1.6.0...v2.0.0

v2.0.0-beta.25 - track deltas

04 Sep 02:38
Compare
Choose a tag to compare
Pre-release

Allows for runtime perf which results in a larger bundle size. More space efficient but less time efficient.

819 B --> 848 B (3.54% increase)

Usage

Store state delta rather than full object

diff?: (pastState: Partial<PartialTState>, currentState: Partial<PartialTState>) => Partial<PartialTState> | null

For 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.

If 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.

You can write your own or use something like microdiff, just-diff, or deep-object-diff.

const useStore = create<StoreState>(
  temporal(
    (set) => ({
      // your store fields
    }),
    {
      diff: (pastState, currentState) => {
        const myDiff = diff(currentState, pastState);
        const newStateFromDiff = myDiff.reduce(
          (acc, difference) => {
            type Key = keyof typeof currentState;
            if (difference.type === 'CHANGE') {
              const pathAsString = difference.path.join('.') as Key;
              acc[pathAsString] = difference.value;
            }
            return acc;
          },
          {} as Partial<typeof currentState>,
        );
        return isEmpty(newStateFromDiff) ? null : newStateFromDiff;
      },
    },
  ),
);

What's Changed

Full Changelog: v2.0.0-beta.24...v2.0.0-beta.25

v2.0.0-beta.24 - Update deps, smaller build

01 Aug 01:33
Compare
Choose a tag to compare

821 B --> 819 B

What's Changed

Full Changelog: v2.0.0-beta.23...v2.0.0-beta.24

v2.0.0-beta.23 - Use Array methods

18 Jun 01:41
Compare
Choose a tag to compare
Pre-release

833 B --> 821 B

Switch from using a while loop to using array methods.
This allows us to shrink the bundle size and to only perform array copies if we are actually going to commit something to the store.

What's Changed

Full Changelog: v2.0.0-beta.22...v2.0.0-beta.23