Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Zustand 4.5.0+ and Introduce Mutative Middleware #92

Merged
merged 28 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .changeset/shy-bats-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
'zustand-x': major
---

- Added support for Zustand 4.5.0+.
- `mutative` support. Pass `mutative: true` in the options.

## Migration Instructions

Update the Store Initialization:

1. Replace the old method of initializing the store with the new API.

```tsx
const store = createStore(
() => ({
name: 'zustandX',
stars: 0,
}),
{
name: 'repo',
immer: true,
}
);
```

or

```tsx
const store = createStore({
name: 'zustandX',
stars: 0,
},
{
name: 'repo',
immer: true,
}
);
```

2. Ensure to pass the configuration object with name and other options as needed.
3. If your application relies on immer, enable it by passing immer: true in the configuration object.

```tsx
const store = createStore(
() => ({
name: 'zustandX',
stars: 0,
}),
{
name: 'repo',
immer: true,
}
);
```

4. With the new version, integrating middlewares has also changed. Here's how to upgrade your middleware usage:

```tsx
const store = createStore(
middlewareWrapHere(() => ({
name: 'zustandX',
stars: 0,
})),
{
name: 'repo',
immer: true,
}
);
```
143 changes: 86 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
> [!NOTE]
> `@udecode/zustood` has been renamed to `zustand-x`.
> [!NOTE] > `@udecode/zustood` has been renamed to `zustand-x`.
> Using Jotai? See [JotaiX](https://github.com/udecode/jotai-x).

# ZustandX
Expand All @@ -21,12 +20,9 @@ code.
which solves these challenges, so you can focus on your app.

```bash
yarn add zustand@4.4.7 zustand-x
yarn add zustand@latest zustand-x
```

> [!IMPORTANT]
> `zustand` 4.5.0+ is not yet supported. See https://github.com/udecode/zustand-x/issues/79.

Visit [zustand-x.udecode.dev](https://zustand-x.udecode.dev) for the
API.

Expand All @@ -36,23 +32,28 @@ API.
- Modular state management:
- Derived selectors
- Derived actions
- `immer`, `devtools` and `persist` middlewares
- `immer`, `devtools` , `mutative` and `persist` middlewares
- Full typescript support
- `react-tracked` support

## Create a store

```ts
import { createStore } from 'zustand-x'

const repoStore = createStore('repo')({
name: 'zustandX',
stars: 0,
owner: {
name: 'someone',
email: '[email protected]',
import { createStore } from 'zustand-x';

const repoStore = createStore(
{
name: 'zustandX',
stars: 0,
owner: {
name: 'someone',
email: '[email protected]',
},
},
})
{
name: 'repo',
}
);
```

- the parameter of the first function is the name of the store, this is
Expand All @@ -66,10 +67,10 @@ Note that the zustand store is accessible through:

```ts
// hook store
repoStore.useStore
repoStore.useStore;

// vanilla store
repoStore.store
repoStore.store;
```

## Selectors
Expand All @@ -80,8 +81,8 @@ Use the hooks in React components, no providers needed. Select your
state and the component will re-render on changes. Use the `use` method:

```ts
repoStore.use.name()
repoStore.use.stars()
repoStore.use.name();
repoStore.use.stars();
```

We recommend using the global hooks (see below) to support ESLint hook
Expand All @@ -95,7 +96,7 @@ Use the tracked hooks in React components, no providers needed. Select your
state and the component will trigger re-renders only if the **accessed property** is changed. Use the `useTracked` method:

```ts
repoStore.useTracked.owner()
repoStore.useTracked.owner();
```

### Getters
Expand All @@ -104,14 +105,14 @@ Don't overuse hooks. If you don't need to subscribe to the state, use
instead the `get` method:

```ts
repoStore.get.name()
repoStore.get.stars()
repoStore.get.name();
repoStore.get.stars();
```

You can also get the whole state:

```ts
repoStore.get.state()
repoStore.get.state();
```

### Extend selectors
Expand All @@ -121,11 +122,16 @@ selectors) for reusability. ZustandX supports extending selectors with
full typescript support:

```ts
const repoStore = createStore('repo')({
name: 'zustandX',
stars: 0,
middlewares: ['immer', 'devtools', 'persist']
})
const repoStore = createStore(
{
name: 'zustandX',
stars: 0,
middlewares: ['immer', 'devtools', 'persist'],
},
{
name: 'repo',
}
)
.extendSelectors((state, get, api) => ({
validName: () => get.name().trim(),
// other selectors
Expand All @@ -134,17 +140,17 @@ const repoStore = createStore('repo')({
// get.validName is accessible
title: (prefix: string) =>
`${prefix + get.validName()} with ${get.stars()} stars`,
}))
// extend again...
}));
// extend again...
```

## Actions

Update your store from anywhere by using the `set` method:

```ts
repoStore.set.name('new name')
repoStore.set.stars(repoStore.get.stars + 1)
repoStore.set.name('new name');
repoStore.set.stars(repoStore.get.stars + 1);
```

### Extend actions
Expand All @@ -155,17 +161,23 @@ You can update the whole state from your app:
store.set.state((draft) => {
draft.name = 'test';
draft.stars = 1;
return draft;
});
```

However, you generally want to create derived actions for reusability.
ZustandX supports extending actions with full typescript support:

```ts
const repoStore = createStore('repo')({
name: 'zustandX',
stars: 0,
})
const repoStore = createStore(
{
name: 'zustandX',
stars: 0,
},
{
name: 'repo',
}
)
.extendActions((set, get, api) => ({
validName: (name: string) => {
set.name(name.trim());
Expand All @@ -178,8 +190,8 @@ const repoStore = createStore('repo')({
set.validName(name);
set.stars(0);
},
}))
// extend again...
}));
// extend again...
```

## Global store
Expand Down Expand Up @@ -218,14 +230,8 @@ export const actions = mapValuesKey('set', rootStore);
### Global hook selectors

```ts
import shallow from 'zustand/shallow'

useStore().repo.name()
useStore().modal.isOpen()

// prevent unnecessary re-renders
// more see: https://docs.pmnd.rs/zustand/recipes#selecting-multiple-state-slices
useStore().repo.middlewares(shallow)
useStore().repo.name();
useStore().modal.isOpen();
```

### Global tracked hook selectors
Expand Down Expand Up @@ -258,17 +264,17 @@ By using `useStore() or useTrackStore()`, ESLint will correctly lint hook errors
### Global getter selectors

```ts
store.repo.name()
store.modal.isOpen()
store.repo.name();
store.modal.isOpen();
```

These can be used anywhere.

### Global actions

```ts
actions.repo.stars(store.repo.stars + 1)
actions.modal.open()
actions.repo.stars(store.repo.stars + 1);
actions.modal.open();
```

These can be used anywhere.
Expand All @@ -278,24 +284,47 @@ These can be used anywhere.
The second parameter of `createStore` is for options:

```ts
export interface CreateStoreOptions<T extends State> {
middlewares?: any[];
export interface CreateStoreOptions {
name: string;
devtools?: DevtoolsOptions;
immer?: ImmerOptions;
mutative?: MutativeOptions;
persist?: PersistOptions;
}
```

### Middlewares

ZustandX is using these middlewares:
- `immer`: required. Autofreeze can be enabled using
`immer.enabledAutoFreeze` option.
- `devtools`: enabled if `devtools.enabled` option is `true`.

- `immer`: enabled if `immer.enabled` option is `true`. `immer` implements from [zustand](https://github.com/pmndrs/zustand?tab=readme-ov-file#immer-middleware).
- `devtools`: enabled if `devtools.enabled` option is `true`. `devtools` implements `DevtoolsOptions` interface from [zustand](https://github.com/pmndrs/zustand?tab=readme-ov-file#redux-devtools).
- `mutative`: enabled if `mutative.enabled` option is `true`.
- `persist`: enabled if `persist.enabled` option is `true`. `persist`
implements `PersistOptions` interface from
[zustand](https://github.com/pmndrs/zustand#persist-middleware)
- custom middlewares can be added using `middlewares` option
- custom middlewares can be added by wrapping `state initiator`.

```ts
import { createStore } from 'zustand-x';
import { combine } from 'zustand/middleware';

const store = createStore<{ name: string; stars?: number }>(
combine(
() => ({
name: 'zustandX',
}),
() => ({ stars: 0 })
),
{
name: 'repo',
//enables persist middleware
persist: {
enabled: true,
},
}
);
```

## Contributing and project organization

Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,22 @@
"prettier": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "18.2.0",
"react-test-renderer": "18.2.0",
"rimraf": "^5.0.5",
"ts-jest": "^29.1.1",
"tsup": "8.0.1",
"turbo": "^1.11.0",
"turbowatch": "2.29.4",
"typedoc": "^0.25.4",
"typescript": "5.3.3",
"zustand": "^4.4.7"
"typescript": "5.3.3"
},
"packageManager": "[email protected]",
"engines": {
"node": ">=18.12.0",
"yarn": ">=1.22.0",
"npm": "please-use-yarn"
},
"dependencies": {
"zustand": "5.0.2"
}
}
Loading
Loading