CoolFace
Apppublic

AK-21/Graphite-Industrial-Intelligence

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
readme.md506 linesDownload Raw Back to zustand
1<p align="center">2  <img src="bear.jpg" />3</p>4 5[![Build Status](https://img.shields.io/github/actions/workflow/status/pmndrs/zustand/lint-and-type.yml?branch=main&style=flat&colorA=000000&colorB=000000)](https://github.com/pmndrs/zustand/actions?query=workflow%3ALint)6[![Build Size](https://img.shields.io/bundlephobia/minzip/zustand?label=bundle%20size&style=flat&colorA=000000&colorB=000000)](https://bundlephobia.com/result?p=zustand)7[![Version](https://img.shields.io/npm/v/zustand?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/zustand)8[![Downloads](https://img.shields.io/npm/dt/zustand.svg?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/zustand)9[![Discord Shield](https://img.shields.io/discord/740090768164651008?style=flat&colorA=000000&colorB=000000&label=discord&logo=discord&logoColor=ffffff)](https://discord.gg/poimandres)10 11A small, fast and scalable bearbones state-management solution using simplified flux principles. Has a comfy API based on hooks, isn't boilerplatey or opinionated.12 13Don't disregard it because it's cute. It has quite the claws, lots of time was spent dealing with common pitfalls, like the dreaded [zombie child problem](https://react-redux.js.org/api/hooks#stale-props-and-zombie-children), [react concurrency](https://github.com/bvaughn/rfcs/blob/useMutableSource/text/0000-use-mutable-source.md), and [context loss](https://github.com/facebook/react/issues/13332) between mixed renderers. It may be the one state-manager in the React space that gets all of these right.14 15You can try a live demo [here](https://githubbox.com/pmndrs/zustand/tree/main/examples/demo).16 17```bash18npm i zustand19```20 21:warning: This readme is written for JavaScript users. If you are a TypeScript user, be sure to check out our [TypeScript Usage section](#typescript-usage).22 23## First create a store24 25Your store is a hook! You can put anything in it: primitives, objects, functions. State has to be updated immutably and the `set` function [merges state](./docs/guides/immutable-state-and-merging.md) to help it.26 27```jsx28import { create } from 'zustand'29 30const useBearStore = create((set) => ({31  bears: 0,32  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),33  removeAllBears: () => set({ bears: 0 }),34}))35```36 37## Then bind your components, and that's it!38 39Use the hook anywhere, no providers are needed. Select your state and the component will re-render on changes.40 41```jsx42function BearCounter() {43  const bears = useBearStore((state) => state.bears)44  return <h1>{bears} around here ...</h1>45}46 47function Controls() {48  const increasePopulation = useBearStore((state) => state.increasePopulation)49  return <button onClick={increasePopulation}>one up</button>50}51```52 53### Why zustand over redux?54 55- Simple and un-opinionated56- Makes hooks the primary means of consuming state57- Doesn't wrap your app in context providers58- [Can inform components transiently (without causing render)](#transient-updates-for-often-occurring-state-changes)59 60### Why zustand over context?61 62- Less boilerplate63- Renders components only on changes64- Centralized, action-based state management65 66---67 68# Recipes69 70## Fetching everything71 72You can, but bear in mind that it will cause the component to update on every state change!73 74```jsx75const state = useBearStore()76```77 78## Selecting multiple state slices79 80It detects changes with strict-equality (old === new) by default, this is efficient for atomic state picks.81 82```jsx83const nuts = useBearStore((state) => state.nuts)84const honey = useBearStore((state) => state.honey)85```86 87If you want to construct a single object with multiple state-picks inside, similar to redux's mapStateToProps, you can use [useShallow](./docs/guides/prevent-rerenders-with-use-shallow.md) to prevent unnecessary rerenders when the selector output does not change according to shallow equal.88 89```jsx90import { create } from 'zustand'91import { useShallow } from 'zustand/react/shallow'92 93const useBearStore = create((set) => ({94  bears: 0,95  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),96  removeAllBears: () => set({ bears: 0 }),97}))98 99// Object pick, re-renders the component when either state.nuts or state.honey change100const { nuts, honey } = useBearStore(101  useShallow((state) => ({ nuts: state.nuts, honey: state.honey })),102)103 104// Array pick, re-renders the component when either state.nuts or state.honey change105const [nuts, honey] = useBearStore(106  useShallow((state) => [state.nuts, state.honey]),107)108 109// Mapped picks, re-renders the component when state.treats changes in order, count or keys110const treats = useBearStore(useShallow((state) => Object.keys(state.treats)))111```112 113For more control over re-rendering, you may provide any custom equality function.114 115```jsx116const treats = useBearStore(117  (state) => state.treats,118  (oldTreats, newTreats) => compare(oldTreats, newTreats),119)120```121 122## Overwriting state123 124The `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.125 126```jsx127import omit from 'lodash-es/omit'128 129const useFishStore = create((set) => ({130  salmon: 1,131  tuna: 2,132  deleteEverything: () => set({}, true), // clears the entire store, actions included133  deleteTuna: () => set((state) => omit(state, ['tuna']), true),134}))135```136 137## Async actions138 139Just call `set` when you're ready, zustand doesn't care if your actions are async or not.140 141```jsx142const useFishStore = create((set) => ({143  fishies: {},144  fetch: async (pond) => {145    const response = await fetch(pond)146    set({ fishies: await response.json() })147  },148}))149```150 151## Read from state in actions152 153`set` allows fn-updates `set(state => result)`, but you still have access to state outside of it through `get`.154 155```jsx156const useSoundStore = create((set, get) => ({157  sound: 'grunt',158  action: () => {159    const sound = get().sound160    ...161```162 163## Reading/writing state and reacting to changes outside of components164 165Sometimes 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.166 167:warning: This technique is not recommended for adding state in [React Server Components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md) (typically in Next.js 13 and above). It can lead to unexpected bugs and privacy issues for your users. For more details, see [#2200](https://github.com/pmndrs/zustand/discussions/2200).168 169```jsx170const useDogStore = create(() => ({ paw: true, snout: true, fur: true }))171 172// Getting non-reactive fresh state173const paw = useDogStore.getState().paw174// Listening to all changes, fires synchronously on every change175const unsub1 = useDogStore.subscribe(console.log)176// Updating state, will trigger listeners177useDogStore.setState({ paw: false })178// Unsubscribe listeners179unsub1()180 181// You can of course use the hook as you always would182function Component() {183  const paw = useDogStore((state) => state.paw)184  ...185```186 187### Using subscribe with selector188 189If you need to subscribe with a selector,190`subscribeWithSelector` middleware will help.191 192With this middleware `subscribe` accepts an additional signature:193 194```ts195subscribe(selector, callback, options?: { equalityFn, fireImmediately }): Unsubscribe196```197 198```js199import { subscribeWithSelector } from 'zustand/middleware'200const useDogStore = create(201  subscribeWithSelector(() => ({ paw: true, snout: true, fur: true })),202)203 204// Listening to selected changes, in this case when "paw" changes205const unsub2 = useDogStore.subscribe((state) => state.paw, console.log)206// Subscribe also exposes the previous value207const unsub3 = useDogStore.subscribe(208  (state) => state.paw,209  (paw, previousPaw) => console.log(paw, previousPaw),210)211// Subscribe also supports an optional equality function212const unsub4 = useDogStore.subscribe(213  (state) => [state.paw, state.fur],214  console.log,215  { equalityFn: shallow },216)217// Subscribe and fire immediately218const unsub5 = useDogStore.subscribe((state) => state.paw, console.log, {219  fireImmediately: true,220})221```222 223## Using zustand without React224 225Zustand 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.226 227```jsx228import { createStore } from 'zustand/vanilla'229 230const store = createStore((set) => ...)231const { getState, setState, subscribe, getInitialState } = store232 233export default store234```235 236You can use a vanilla store with `useStore` hook available since v4.237 238```jsx239import { useStore } from 'zustand'240import { vanillaStore } from './vanillaStore'241 242const useBoundStore = (selector) => useStore(vanillaStore, selector)243```244 245:warning: Note that middlewares that modify `set` or `get` are not applied to `getState` and `setState`.246 247## Transient updates (for often occurring state-changes)248 249The subscribe function allows components to bind to a state-portion without forcing re-render on changes. Best 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.250 251```jsx252const useScratchStore = create((set) => ({ scratches: 0, ... }))253 254const Component = () => {255  // Fetch initial state256  const scratchRef = useRef(useScratchStore.getState().scratches)257  // Connect to the store on mount, disconnect on unmount, catch state-changes in a reference258  useEffect(() => useScratchStore.subscribe(259    state => (scratchRef.current = state.scratches)260  ), [])261  ...262```263 264## Sick of reducers and changing nested states? Use Immer!265 266Reducing nested structures is tiresome. Have you tried [immer](https://github.com/mweststrate/immer)?267 268```jsx269import { produce } from 'immer'270 271const useLushStore = create((set) => ({272  lush: { forest: { contains: { a: 'bear' } } },273  clearForest: () =>274    set(275      produce((state) => {276        state.lush.forest.contains = null277      }),278    ),279}))280 281const clearForest = useLushStore((state) => state.clearForest)282clearForest()283```284 285[Alternatively, there are some other solutions.](./docs/guides/updating-state.md#with-immer)286 287## Persist middleware288 289You can persist your store's data using any kind of storage.290 291```jsx292import { create } from 'zustand'293import { persist, createJSONStorage } from 'zustand/middleware'294 295const useFishStore = create(296  persist(297    (set, get) => ({298      fishes: 0,299      addAFish: () => set({ fishes: get().fishes + 1 }),300    }),301    {302      name: 'food-storage', // name of the item in the storage (must be unique)303      storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used304    },305  ),306)307```308 309[See the full documentation for this middleware.](./docs/integrations/persisting-store-data.md)310 311## Immer middleware312 313Immer is available as middleware too.314 315```jsx316import { create } from 'zustand'317import { immer } from 'zustand/middleware/immer'318 319const useBeeStore = create(320  immer((set) => ({321    bees: 0,322    addBees: (by) =>323      set((state) => {324        state.bees += by325      }),326  })),327)328```329 330## Can't live without redux-like reducers and action types?331 332```jsx333const types = { increase: 'INCREASE', decrease: 'DECREASE' }334 335const reducer = (state, { type, by = 1 }) => {336  switch (type) {337    case types.increase:338      return { grumpiness: state.grumpiness + by }339    case types.decrease:340      return { grumpiness: state.grumpiness - by }341  }342}343 344const useGrumpyStore = create((set) => ({345  grumpiness: 0,346  dispatch: (args) => set((state) => reducer(state, args)),347}))348 349const dispatch = useGrumpyStore((state) => state.dispatch)350dispatch({ type: types.increase, by: 2 })351```352 353Or, just use our redux-middleware. It wires up your main-reducer, sets the initial state, and adds a dispatch function to the state itself and the vanilla API.354 355```jsx356import { redux } from 'zustand/middleware'357 358const useGrumpyStore = create(redux(reducer, initialState))359```360 361## Redux devtools362 363```jsx364import { devtools } from 'zustand/middleware'365 366// Usage with a plain action store, it will log actions as "setState"367const usePlainStore = create(devtools((set) => ...))368// Usage with a redux store, it will log full action types369const useReduxStore = create(devtools(redux(reducer, initialState)))370```371 372One redux devtools connection for multiple stores373 374```jsx375import { devtools } from 'zustand/middleware'376 377// Usage with a plain action store, it will log actions as "setState"378const usePlainStore1 = create(devtools((set) => ..., { name, store: storeName1 }))379const usePlainStore2 = create(devtools((set) => ..., { name, store: storeName2 }))380// Usage with a redux store, it will log full action types381const useReduxStore = create(devtools(redux(reducer, initialState)), , { name, store: storeName3 })382const useReduxStore = create(devtools(redux(reducer, initialState)), , { name, store: storeName4 })383```384 385Assigning different connection names will separate stores in redux devtools. This also helps group different stores into separate redux devtools connections.386 387devtools takes the store function as its first argument, optionally you can name the store or configure [serialize](https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md#serialize) options with a second argument.388 389Name store: `devtools(..., {name: "MyStore"})`, which will create a separate instance named "MyStore" in the devtools.390 391Serialize options: `devtools(..., { serialize: { options: true } })`.392 393#### Logging Actions394 395devtools will only log actions from each separated store unlike in a typical _combined reducers_ redux store. See an approach to combining stores https://github.com/pmndrs/zustand/issues/163396 397You can log a specific action type for each `set` function by passing a third parameter:398 399```jsx400const useBearStore = create(devtools((set) => ({401  ...402  eatFish: () => set(403    (prev) => ({ fishes: prev.fishes > 1 ? prev.fishes - 1 : 0 }),404    undefined,405    'bear/eatFish'406  ),407  ...408```409 410You can also log the action's type along with its payload:411 412```jsx413  ...414  addFishes: (count) => set(415    (prev) => ({ fishes: prev.fishes + count }),416    undefined,417    { type: 'bear/addFishes', count, }418  ),419  ...420```421 422If an action type is not provided, it is defaulted to "anonymous". You can customize this default value by providing an `anonymousActionType` parameter:423 424```jsx425devtools(..., { anonymousActionType: 'unknown', ... })426```427 428If you wish to disable devtools (on production for instance). You can customize this setting by providing the `enabled` parameter:429 430```jsx431devtools(..., { enabled: false, ... })432```433 434## React context435 436The store created with `create` doesn't require context providers. In some cases, you may want to use contexts for dependency injection or if you want to initialize your store with props from a component. Because the normal store is a hook, passing it as a normal context value may violate the rules of hooks.437 438The recommended method available since v4 is to use the vanilla store.439 440```jsx441import { createContext, useContext } from 'react'442import { createStore, useStore } from 'zustand'443 444const store = createStore(...) // vanilla store without hooks445 446const StoreContext = createContext()447 448const App = () => (449  <StoreContext.Provider value={store}>450    ...451  </StoreContext.Provider>452)453 454const Component = () => {455  const store = useContext(StoreContext)456  const slice = useStore(store, selector)457  ...458```459 460## TypeScript Usage461 462Basic typescript usage doesn't require anything special except for writing `create<State>()(...)` instead of `create(...)`...463 464```ts465import { create } from 'zustand'466import { devtools, persist } from 'zustand/middleware'467import type {} from '@redux-devtools/extension' // required for devtools typing468 469interface BearState {470  bears: number471  increase: (by: number) => void472}473 474const useBearStore = create<BearState>()(475  devtools(476    persist(477      (set) => ({478        bears: 0,479        increase: (by) => set((state) => ({ bears: state.bears + by })),480      }),481      {482        name: 'bear-storage',483      },484    ),485  ),486)487```488 489A more complete TypeScript guide is [here](docs/guides/typescript.md).490 491## Best practices492 493- You may wonder how to organize your code for better maintenance: [Splitting the store into separate slices](./docs/guides/slices-pattern.md).494- Recommended usage for this unopinionated library: [Flux inspired practice](./docs/guides/flux-inspired-practice.md).495- [Calling actions outside a React event handler in pre-React 18](./docs/guides/event-handler-in-pre-react-18.md).496- [Testing](./docs/guides/testing.md)497- For more, have a look [in the docs folder](./docs/)498 499## Third-Party Libraries500 501Some users may want to extend Zustand's feature set which can be done using third-party libraries made by the community. For information regarding third-party libraries with Zustand, visit [the doc](./docs/integrations/third-party-libraries.md).502 503## Comparison with other libraries504 505- [Difference between zustand and other state management libraries for React](https://docs.pmnd.rs/zustand/getting-started/comparison)506