mirror of
https://github.com/pmndrs/zustand.git
synced 2025-12-08 19:45:52 +00:00
Remove recipes (#2085)
* Clean up files * Update readme * Minor changes * Update readme.md Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com> * Update readme.md Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com> * Update readme.md * Update readme.md Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com> --------- Co-authored-by: Daishi Kato <dai-shi@users.noreply.github.com>
This commit is contained in:
parent
8e8156f142
commit
60d7116a5e
@ -1,456 +0,0 @@
|
||||
---
|
||||
title: Recipes
|
||||
description: How to do all you need with Zustand
|
||||
nav: 15
|
||||
---
|
||||
|
||||
## Fetching everything
|
||||
|
||||
You can, but bear in mind that it will cause
|
||||
the component to update on every state change!
|
||||
|
||||
```jsx
|
||||
const state = useStore()
|
||||
```
|
||||
|
||||
## Selecting multiple state slices
|
||||
|
||||
It detects changes with strict-equality (`old === new`) by default.
|
||||
This is efficient for atomic state picks.
|
||||
|
||||
```jsx
|
||||
const nuts = useStore((state) => state.nuts)
|
||||
const honey = useStore((state) => state.honey)
|
||||
```
|
||||
|
||||
For more control over re-rendering,
|
||||
you may provide an alternative equality function on the second argument.
|
||||
|
||||
```jsx
|
||||
const treats = useStore(
|
||||
(state) => state.treats,
|
||||
(oldTreats, newTreats) => compare(oldTreats, newTreats)
|
||||
)
|
||||
```
|
||||
|
||||
For instance, if you want to construct
|
||||
a single object with multiple state-picks inside,
|
||||
similar to Redux's `mapStateToProps`,
|
||||
you can tell Zustand that you want the object
|
||||
to be diffed shallowly by passing the `shallow` equality function.
|
||||
|
||||
```jsx
|
||||
import { shallow } from 'zustand/shallow'
|
||||
|
||||
// Object pick, re-renders the component when either state.nuts or state.honey change
|
||||
const { nuts, honey } = useStore(
|
||||
(state) => ({ nuts: state.nuts, honey: state.honey }),
|
||||
shallow
|
||||
)
|
||||
|
||||
// Array pick, re-renders the component when either state.nuts or state.honey change
|
||||
const [nuts, honey] = useStore((state) => [state.nuts, state.honey], shallow)
|
||||
|
||||
// Mapped picks, re-renders the component when state.treats changes in order, count or keys
|
||||
const treats = useStore((state) => Object.keys(state.treats), shallow)
|
||||
```
|
||||
|
||||
## Fetching from multiple stores
|
||||
|
||||
Since you can create as many stores as you like,
|
||||
forwarding results to succeeding selectors is as natural as it gets.
|
||||
|
||||
```jsx
|
||||
const currentBear = useCredentialsStore((state) => state.currentBear)
|
||||
const bear = useBearStore((state) => state.bears[currentBear])
|
||||
```
|
||||
|
||||
## Overwriting state
|
||||
|
||||
The `set` function has a second argument, `false` by default.
|
||||
Instead of merging, it will replace the state model.
|
||||
Be careful not to wipe out parts you rely on, like actions.
|
||||
|
||||
```jsx
|
||||
import omit from 'lodash-es/omit'
|
||||
|
||||
const useStore = create((set) => ({
|
||||
salmon: 1,
|
||||
tuna: 2,
|
||||
deleteEverything: () => set({}, true), // clears the entire store, actions included
|
||||
deleteTuna: () => set((state) => omit(state, ['tuna']), true),
|
||||
}))
|
||||
```
|
||||
|
||||
## Async actions
|
||||
|
||||
Just call `set` when you're ready,
|
||||
zustand doesn't care if your actions are async or not.
|
||||
|
||||
```jsx
|
||||
const useStore = create((set) => ({
|
||||
fishies: {},
|
||||
fetch: async (pond) => {
|
||||
const response = await fetch(pond)
|
||||
set({ fishies: await response.json() })
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
## Read from state in actions
|
||||
|
||||
`set` allows fn-updates `set(state => result)`,
|
||||
but you still have access to state outside of it through `get`.
|
||||
|
||||
```jsx
|
||||
const useStore = create((set, get) => ({
|
||||
sound: 'grunt',
|
||||
action: () => {
|
||||
const sound = get().sound
|
||||
// ...
|
||||
},
|
||||
}))
|
||||
```
|
||||
|
||||
## Reading/writing state and reacting to changes outside of components
|
||||
|
||||
Sometimes you need to access state in a non-reactive way,
|
||||
or act upon the store.
|
||||
For these cases the resulting hook
|
||||
has utility functions attached to its prototype.
|
||||
|
||||
If you need to subscribe with selector,
|
||||
`subscribeWithSelector` middleware will help.
|
||||
With this middleware, subscribe accepts an additional signature:
|
||||
|
||||
```jsx
|
||||
subscribe(selector, callback, options?: { equalityFn, fireImmediately }): Unsubscribe
|
||||
```
|
||||
|
||||
```jsx
|
||||
import { create } from 'zustand'
|
||||
import { subscribeWithSelector } from 'zustand/middleware'
|
||||
import { shallow } from 'zustand/shallow'
|
||||
const useStore = create(
|
||||
subscribeWithSelector(() => ({ paw: true, snout: true, fur: true }))
|
||||
)
|
||||
|
||||
// Getting non-reactive fresh state
|
||||
const paw = useStore.getState().paw
|
||||
// Listening to all changes, fires on every change
|
||||
const unsub1 = useStore.subscribe(console.log)
|
||||
// Listening to selected changes, in this case when "paw" changes
|
||||
const unsub2 = useStore.subscribe((state) => state.paw, console.log)
|
||||
// Subscribe also supports an optional equality function
|
||||
const unsub3 = useStore.subscribe(
|
||||
(state) => [state.paw, state.fur],
|
||||
console.log,
|
||||
{ equalityFn: shallow }
|
||||
)
|
||||
// Subscribe also exposes the previous value
|
||||
const unsub4 = useStore.subscribe(
|
||||
(state) => state.paw,
|
||||
(paw, previousPaw) => console.log(paw, previousPaw)
|
||||
)
|
||||
// Updating state, will trigger listeners
|
||||
useStore.setState({ paw: false })
|
||||
useStore.setState({ snout: false })
|
||||
// Unsubscribe listeners
|
||||
unsub1()
|
||||
unsub2()
|
||||
unsub3()
|
||||
unsub4()
|
||||
// Destroying the store (removing all listeners)
|
||||
useStore.destroy()
|
||||
|
||||
// You can of course use the hook as you always would
|
||||
function Component() {
|
||||
const paw = useStore((state) => state.paw)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Using zustand without React
|
||||
|
||||
Zustand's core can be imported and used without the React dependency.
|
||||
The only difference is that the create function does not return a hook,
|
||||
but the API utilities.
|
||||
|
||||
```jsx
|
||||
import { createStore } from 'zustand/vanilla'
|
||||
|
||||
const store = createStore(() => ({ ... }))
|
||||
const { getState, setState, subscribe, destroy } = store
|
||||
```
|
||||
|
||||
You can use a vanilla store in React with a `useStore` hook.
|
||||
|
||||
```jsx
|
||||
import { useStore } from 'zustand'
|
||||
import { vanillaStore } from './vanillaStore'
|
||||
|
||||
const useBoundStore = (selector) => useStore(vanillaStore, selector)
|
||||
```
|
||||
|
||||
## Transient updates (for frequent state changes)
|
||||
|
||||
The `subscribe` function allows components to bind
|
||||
to a state portion without forcing a re-render on changes.
|
||||
It is best to combine it with `useEffect`
|
||||
for automatic unsubscribe on unmount.
|
||||
This can make a [drastic](https://codesandbox.io/s/peaceful-johnson-txtws)
|
||||
performance impact, when you are allowed to mutate the view directly.
|
||||
|
||||
```jsx
|
||||
const useScratchStore = create(set => ({ scratches: 0, ... }))
|
||||
|
||||
function Component() {
|
||||
// Fetch initial state
|
||||
const scratchRef = useRef(useScratchStore.getState().scratches)
|
||||
// Connect to the store on mount, disconnect on unmount, catch state-changes in a reference
|
||||
useEffect(() => useScratchStore.subscribe(
|
||||
(state) => (scratchRef.current = state.scratches)
|
||||
), [])
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Sick of reducers and changing nested state? Use Immer!
|
||||
|
||||
Reducing nested structures is tiresome.
|
||||
Have you tried [Immer](https://github.com/immerjs/immer)?
|
||||
|
||||
```jsx
|
||||
import { produce } from 'immer'
|
||||
|
||||
const useStore = create((set) => ({
|
||||
lush: { forest: { contains: { a: 'bear' } } },
|
||||
set: (fn) => set(produce(fn)),
|
||||
}))
|
||||
|
||||
const set = useStore((state) => state.set)
|
||||
set((state) => {
|
||||
state.lush.forest.contains = null
|
||||
})
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
You can functionally compose your store any way you like.
|
||||
|
||||
```jsx
|
||||
// Log every time state is changed
|
||||
const log = (config) => (set, get, api) =>
|
||||
config(
|
||||
(args) => {
|
||||
console.log(' applying', args)
|
||||
set(args)
|
||||
console.log(' new state', get())
|
||||
},
|
||||
get,
|
||||
api
|
||||
)
|
||||
|
||||
// Turn the set method into an immer proxy
|
||||
const immer = (config) => (set, get, api) =>
|
||||
config((fn) => set(produce(fn)), get, api)
|
||||
|
||||
const useStore = create(
|
||||
log(
|
||||
immer((set) => ({
|
||||
bees: false,
|
||||
setBees: (input) => set((state) => void (state.bees = input)),
|
||||
}))
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>How to pipe middlewares</summary>
|
||||
|
||||
```js
|
||||
import { create } from 'zustand'
|
||||
import { produce } from 'immer'
|
||||
import pipe from 'ramda/es/pipe'
|
||||
|
||||
/* log and immer functions from previous example */
|
||||
/* you can pipe as many middlewares as you want */
|
||||
const createStore = pipe(log, immer, create)
|
||||
|
||||
const useStore = createStore((set) => ({
|
||||
bears: 1,
|
||||
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
|
||||
}))
|
||||
|
||||
export default useStore
|
||||
```
|
||||
|
||||
For a TS example see the following [discussion](https://github.com/pmndrs/zustand/discussions/224#discussioncomment-118208)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>How to type immer middleware in TypeScript</summary>
|
||||
|
||||
```ts
|
||||
import { State, StateCreator } from 'zustand'
|
||||
import { produce, Draft } from 'immer'
|
||||
|
||||
// Immer V8 or lower
|
||||
const immer =
|
||||
<T extends State>(
|
||||
config: StateCreator<T, (fn: (draft: Draft<T>) => void) => void>
|
||||
): StateCreator<T> =>
|
||||
(set, get, api) =>
|
||||
config((fn) => set(produce(fn) as (state: T) => T), get, api)
|
||||
|
||||
// Immer V9
|
||||
const immer =
|
||||
<T extends State>(
|
||||
config: StateCreator<T, (fn: (draft: Draft<T>) => void) => void>
|
||||
): StateCreator<T> =>
|
||||
(set, get, api) =>
|
||||
config((fn) => set(produce<T>(fn)), get, api)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Persist middleware
|
||||
|
||||
You can persist your store's data using any kind of storage.
|
||||
|
||||
```jsx
|
||||
import { create } from 'zustand'
|
||||
import { persist, createJSONStorage } from 'zustand/middleware'
|
||||
|
||||
export const useStore = create(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
fishes: 0,
|
||||
addAFish: () => set({ fishes: get().fishes + 1 }),
|
||||
}),
|
||||
{
|
||||
name: 'food-storage', // name of the item in the storage (must be unique)
|
||||
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Can't live without Redux-like reducers and action types?
|
||||
|
||||
```jsx
|
||||
const types = { increase: 'INCREASE', decrease: 'DECREASE' }
|
||||
|
||||
const reducer = (state, { type, by = 1 }) => {
|
||||
switch (type) {
|
||||
case types.increase:
|
||||
return { grumpiness: state.grumpiness + by }
|
||||
case types.decrease:
|
||||
return { grumpiness: state.grumpiness - by }
|
||||
}
|
||||
}
|
||||
|
||||
const useStore = create((set) => ({
|
||||
grumpiness: 0,
|
||||
dispatch: (args) => set((state) => reducer(state, args)),
|
||||
}))
|
||||
|
||||
const dispatch = useStore((state) => state.dispatch)
|
||||
dispatch({ type: types.increase, by: 2 })
|
||||
```
|
||||
|
||||
Or, just use our `redux` middleware.
|
||||
It wires up your main reducer, sets initial state,
|
||||
and adds a dispatch function to the state itself and the vanilla API.
|
||||
|
||||
```jsx
|
||||
import { redux } from 'zustand/middleware'
|
||||
|
||||
const useStore = create(redux(reducer, initialState))
|
||||
```
|
||||
|
||||
## Calling actions outside a React event handler
|
||||
|
||||
Because React handles `setState` synchronously
|
||||
if it's called outside an event handler.
|
||||
Updating the state outside an event handler
|
||||
will force react to update the components synchronously,
|
||||
therefore adding the risk of encountering the zombie-child effect.
|
||||
In order to fix this,
|
||||
the action needs to be wrapped in `unstable_batchedUpdates`.
|
||||
|
||||
```jsx
|
||||
import { unstable_batchedUpdates } from 'react-dom' // or 'react-native'
|
||||
|
||||
const useStore = create((set) => ({
|
||||
fishes: 0,
|
||||
increaseFishes: () => set((prev) => ({ fishes: prev.fishes + 1 })),
|
||||
}))
|
||||
|
||||
const nonReactCallback = () => {
|
||||
unstable_batchedUpdates(() => {
|
||||
useStore.getState().increaseFishes()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
More details in [this issue](https://github.com/pmndrs/zustand/issues/302).
|
||||
|
||||
## Redux devtools
|
||||
|
||||
```jsx
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
// Usage with a plain action store, it will log actions as "setState"
|
||||
const useStore = create(devtools(store))
|
||||
// Usage with a redux store, it will log full action types
|
||||
const useStore = create(devtools(redux(reducer, initialState)))
|
||||
// Disabling devtools (for instance in production build)
|
||||
const useStore = create(devtools(store, { enabled: false }))
|
||||
```
|
||||
|
||||
The `devtools` middleware takes the store function as its first argument.
|
||||
Optionally, you can name the store with a second argument `devtoolsOptions`:
|
||||
`devtools(store, { store: "MyStore" })`, which will be prefixed to your actions.
|
||||
|
||||
`devtools` will only log actions from each separated store,
|
||||
unlike in a typical _combined reducers_ Redux store.
|
||||
See an approach to combining stores [here](https://github.com/pmndrs/zustand/issues/163).
|
||||
|
||||
## TypeScript
|
||||
|
||||
```tsx
|
||||
type State = {
|
||||
bears: number
|
||||
increase: (by: number) => void
|
||||
}
|
||||
|
||||
const useStore = create<State>((set) => ({
|
||||
bears: 0,
|
||||
increase: (by) => set((state) => ({ bears: state.bears + by })),
|
||||
}))
|
||||
```
|
||||
|
||||
You can also use an interface:
|
||||
|
||||
```tsx
|
||||
import { State } from 'zustand'
|
||||
|
||||
interface BearState extends State {
|
||||
bears: number
|
||||
increase: (by: number) => void
|
||||
}
|
||||
```
|
||||
|
||||
Or use `combine` and let `tsc` infer types.
|
||||
|
||||
```tsx
|
||||
import { combine } from 'zustand/middleware'
|
||||
|
||||
const useStore = create(
|
||||
combine({ bears: 0 }, (set) => ({
|
||||
increase: (by: number) => set((state) => ({ bears: state.bears + by })),
|
||||
}))
|
||||
)
|
||||
```
|
||||
@ -165,9 +165,7 @@ const useSoundStore = create((set, get) => ({
|
||||
sound: 'grunt',
|
||||
action: () => {
|
||||
const sound = get().sound
|
||||
// ...
|
||||
},
|
||||
}))
|
||||
...
|
||||
```
|
||||
|
||||
## Reading/writing state and reacting to changes outside of components
|
||||
@ -187,7 +185,7 @@ useDogStore.setState({ paw: false })
|
||||
unsub1()
|
||||
|
||||
// You can of course use the hook as you always would
|
||||
const Component = () => {
|
||||
function Component() {
|
||||
const paw = useDogStore((state) => state.paw)
|
||||
...
|
||||
```
|
||||
@ -332,7 +330,7 @@ const useFishStore = create(
|
||||
addAFish: () => set({ fishes: get().fishes + 1 }),
|
||||
}),
|
||||
{
|
||||
name: 'food-storage', // unique name
|
||||
name: 'food-storage', // name of the item in the storage (must be unique)
|
||||
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
|
||||
}
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user