diff --git a/docs/recipes/recipes.mdx b/docs/recipes/recipes.mdx deleted file mode 100644 index 0bc8a32375..0000000000 --- a/docs/recipes/recipes.mdx +++ /dev/null @@ -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)), - })) - ) -) -``` - -
-How to pipe middlewares - -```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) - -
- -
-How to type immer middleware in TypeScript - -```ts -import { State, StateCreator } from 'zustand' -import { produce, Draft } from 'immer' - -// Immer V8 or lower -const immer = - ( - config: StateCreator) => void) => void> - ): StateCreator => - (set, get, api) => - config((fn) => set(produce(fn) as (state: T) => T), get, api) - -// Immer V9 -const immer = - ( - config: StateCreator) => void) => void> - ): StateCreator => - (set, get, api) => - config((fn) => set(produce(fn)), get, api) -``` - -
- -## 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((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 })), - })) -) -``` diff --git a/readme.md b/readme.md index 486c993641..10e369ad6d 100644 --- a/readme.md +++ b/readme.md @@ -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 } )