Skip to content

Commit

Permalink
Merge pull request #92 from imarabinda/main
Browse files Browse the repository at this point in the history
 Add Support for Zustand 4.5.0+ and Introduce Mutative Middleware
  • Loading branch information
zbeyens authored Jan 5, 2025
2 parents 9054812 + 2fa1da9 commit 91f56e1
Show file tree
Hide file tree
Showing 38 changed files with 1,188 additions and 4,908 deletions.
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

0 comments on commit 91f56e1

Please sign in to comment.