zustand-computed
Version:
A Zustand middleware to create computed states.
151 lines (125 loc) • 4.8 kB
Markdown
# zustand-computed
[![NPM Package][npm-img]][npm-url]
[![Bundle Size][size-img]][size-url]
[![Build Status][build-img]][build-url]
[![Downloads][downloads-img]][downloads-url]
[![Issues][issues-img]][issues-url]
zustand-computed is a lightweight, TypeScript-friendly middleware for the state management system [Zustand](https://github.com/pmndrs/zustand). It's a simple layer which adds a transformation function after any state change in your store.
## Install
```bash
# one of the following
npm i zustand-computed
pnpm i zustand-computed
bun add zustand-computed
yarn add zustand-computed
```
## Usage
The middleware layer takes in your store creation function and a compute function, which transforms your state into a computed state. It does not need to handle merging states.
```js
import { createComputed } from "zustand-computed"
const computed = createComputed((state) => ({
countSq: state.count ** 2,
}))
const useStore = create(
computed(
(set, get) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
dec: () => set((state) => ({ count: state.count - 1 })),
// get() function has access to ComputedStore
square: () => set(() => ({ count: get().countSq })),
root: () => set((state) => ({ count: Math.floor(Math.sqrt(state.count)) })),
})
)
)
```
With types, the previous example would look like this:
```ts
import { createComputed } from "zustand-computed"
type Store = {
count: number
inc: () => void
dec: () => void
}
type ComputedStore = {
countSq: number
}
const computed = createComputed((state: Store): ComputedStore => ({
countSq: state.count ** 2,
}))
const useStore = create<Store>()(
computed(
(set) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
dec: () => set((state) => ({ count: state.count - 1 })),
// get() function has access to ComputedStore
square: () => set(() => ({ count: get().countSq })),
root: () => set((state) => ({ count: Math.floor(Math.sqrt(state.count)) })),
})
)
)
```
The store can then be used as normal in a React component or via the Zustand API.
```tsx
function Counter() {
const { count, countSq, inc, dec } = useStore()
return (
<div>
<span>{count}</span>
<br />
<span>{countSq}</span>
<br />
<button onClick={inc}>+1</button>
<button onClick={dec}>-1</button>
</div>
)
}
```
## With Middleware
Here's an example with the Immer middleware.
```ts
const computed = createComputed((state: Store) => { /* ... */ })
const useStore = create<Store>()(
devtools(
immer(
computed(
(set) => ({
count: 1,
inc: () =>
set((state) => {
// example with Immer middleware
state.count += 1
}),
dec: () => set((state) => ({ count: state.count - 1 })),
})
)
)
)
)
```
## Selectors
By default, when `zustand-computed` runs your `computeState` function, it tracks accessed variables and does not trigger a computation if one of those variables do not change. This could potentially be problematic if you have nested control flow inside of `computeState`, or perhaps you want it to run on _all_ changes regardless of use inside of `computeState`. To disable automatic selector detection, you can pass a second `opts` variable to the `createComputed` function, e.g.
```ts
const computed = createComputed((state: Store) => { /* ... */ }, { disableProxy: true })
const useStore = create<Store, [["chrisvander/zustand-computed", ComputedStore]]>(
computed(
(set) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
dec: () => set((state) => ({ count: state.count - 1 })),
})
)
)
```
Other options include passing a `keys` array, which explicitly spell out the selectors which trigger re-computation. You can also pass a custom `equalityFn`, such as [fast-deep-equal](https://github.com/epoberezkin/fast-deep-equal) instead of the default `zustand/shallow`.
[build-img]: https://github.com/chrisvander/zustand-computed/actions/workflows/ci.yml/badge.svg
[build-url]: https://github.com/chrisvander/zustand-computed/actions/workflows/ci.yml
[size-img]: https://img.shields.io/bundlephobia/minzip/zustand-computed
[size-url]: https://bundlephobia.com/package/zustand-computed@1.4.2
[downloads-img]: https://img.shields.io/npm/dt/zustand-computed
[downloads-url]: https://www.npmtrends.com/zustand-computed
[npm-img]: https://img.shields.io/npm/v/zustand-computed
[npm-url]: https://www.npmjs.com/package/zustand-computed
[issues-img]: https://img.shields.io/github/issues/chrisvander/zustand-computed
[issues-url]: https://github.com/chrisvander/chrisvander/zustand-computed/issues