4.5.2 • Published 1 month ago

zustand v4.5.2

Weekly downloads
51,741
License
MIT
Repository
github
Last release
1 month ago

Build Status npm version

npm install zustand

Small, fast and scaleable bearbones state-management solution. Has a comfy api based on hooks, isn't that boilerplatey or opinionated, but still just enough to be explicit and flux-like. Try a small live demo here.

How to use it

First create a store (or multiple, up to you...)

Your store is a hook! There are no rules, you can put anything in it, atomics, objects, functions. Like Reacts setState, set merges state, and it has the exact same semantics.

import create from 'zustand'

const [useStore] = create(set => ({
  count: 0,
  increase: () => set(state => ({ count: state.count + 1 })),
  reset: () => set({ count: 0 })
}))

Then bind components with the resulting hook, that's it!

Use the hook anywhere, no providers needed. Once you have selected state your component will re-render on changes.

function Counter() {
  const count = useStore(state => state.count)
  return <h1>{count}</h1>
}

function Controls() {
  const increase = useStore(state => state.increase)
  return <button onClick={increase}>up</button>
}

Why zustand over react-redux? This lib ...

  1. is simpler and un-opinionated
  2. makes hooks the primary means of consuming state
  3. isn't strictly dependent on actions, types & dispatch
  4. doesn't wrap your app into context providers (which allows it to support mixed reconcilers)
  5. can access state outside of components (and even React)
  6. has a solution for rapid state changes (look below for transient updates)
  7. is (or can be made) compatible with the redux api

Recipes

Fetching everything

You can, but remember that it will cause the component to update on every state change!

const state = useStore()

Selecting multiple state slices

Just like with Redux's mapStateToProps, useStore can select state, either atomically or by returning an object. It will run a small shallow-equal test over the results you return and update the component on changes only.

const { foo, bar } = useStore(state => ({ foo: state.foo, bar: state.bar }))

Atomic selects do the same ...

const foo = useStore(state => state.foo)
const bar = useStore(state => state.bar)

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.

const currentUser = useCredentialsStore(state => state.currentUser)
const person = usePersonStore(state => state.persons[currentUser])

Memoizing selectors, optimizing performance

Say you select a piece of state ...

const foo = useStore(state => state.foo[props.id])

Your selector (state => state.foo[props.id]) will run on every state change, as well as every time the component renders. It isn't that expensive in this case, but let's optimize it for arguments sake.

You can either pass a static reference:

const fooSelector = useCallback(state => state.foo[props.id], [props.id])
const foo = useStore(fooSelector)

Or an optional dependencies array to let zustand know when the selector needs to update:

const foo = useStore(state => state.foo[props.id], [props.id])

From now on your selector is memoized and will only run when either the state changes, or the selector itself.

Async actions

Just call set when you're ready, it doesn't care if your actions are async or not.

const [useStore] = create(set => ({
  json: {},
  fetch: async url => {
    const response = await fetch(url)
    set({ json: 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.

const [useStore] = create((set, get) => ({
  text: "hello",
  action: () => {
    const text = get().text

Sick of reducers and changing nested state? Use Immer!

Reducing nested structures is tiresome. Have you tried immer?

import produce from "immer"

const [useStore] = create(set => ({
  nested: { structure: { contains: { a: "value" } } },
  set: fn => set(produce(fn)),
}))

const set = useStore(state => state.set)
set(state => void state.nested.structure.contains = null)

Can't live without redux-like reducers and action types?

const types = { increase: "INCREASE", decrease: "DECREASE" }

const reducer = (state, { type, by = 1 }) => {
  switch (type) {
    case types.increase: return { count: state.count + by }
    case types.decrease: return { count: state.count - by }
  }
}

const [useStore] = create(set => ({
  count: 0,
  dispatch: args => set(state => reducer(state, args)),
}))

const dispatch = useStore(state => state.dispatch)
dispatch({ type: types.increase, by: 2 })

Reading/writing state and reacting to changes outside of components

You can use it with or without React out of the box.

const [, api] = create({ a: 1, b: 2, c: 3 })

// Getting fresh state
const num = api.getState().n
// Listening to all changes, fires on every dispatch
const unsub1 = api.subscribe(state => console.log("state changed", state))
// Listening to selected changes
const unsub2 = api.subscribe(state => state.a, a => console.log("a changed", a))
// Updating state, will trigger listeners
api.setState({ a: 1 })
// Unsubscribe listeners
unsub1()
unsub2()
// Destroying the store (removing all listeners)
api.destroy()

Transient updates (for often occuring state-changes)

The api signature of subscribe(selector, callback):unsub allows you to easily bind a component to a store without forcing it to re-render on state changes, you will be notified in a callback instead. Best combine it with useEffect. This can make a drastic performance difference when you are allowed to mutate the view directly.

const [useStore, api] = create(set => ({ [0]: [-10, 0], [1]: [10, 5], ... }))

function Component({ id }) {
  // Fetch initial state
  const xy = useRef(api.getState()[id])
  // Connect to the store on mount, disconnect on unmount, catch state-changes in a callback
  useEffect(() => api.subscribe(state => state[id], coords => (xy.current = coords)), [id])

Middleware

You can functionally compose your store any way you like.

// 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 => ({
  text: "hello",
  setText: input => set(state => {
    state.text = input
  })
}))))

Devtools

Yes, it's currently being hashed out but you can already start using it: https://codesandbox.io/s/amazing-kepler-swxol. It works with regular actions as well, you don't need reducers for this.

gamba-alphagamba-alpha-reactgamba-beta-reactdepo-iq@beskar-labs/command-bar@beskar-labs/glimpse@arturo.soto/rtve-module-layout@mapbox-ts/mapbox-gl-directions@muzman/core@plgwagmi/core@neuvernetzung/cms@triplex/editorcarbonio-calendars-ui@corabank/ibanking@douglasneuroinformatics/react-componentscodeinsightsui@coral-xyz/commonjk-crowdin-libr3f-sdk-testcra-template-buerlimethod-clientstocktrackeraibapiscandinavia@returnoninvestment/base-tablesistecreditoreact-use-async-assets-loader@therms/react-modulesreact-three-scisorminimal-react-stateinbegroad-monohms-video-react@inbegroad-tools/generate-base@inbegroad-tools/mono-adminwn-fm-dreae@teamfabric/fabric-js@teamfabric/storefront-core@puneet_epigraph/epigraph_configurator_uihahnbee-testreact-options-collapsiblehahnbee-test-client@sec-ant/react-flow-rendererw3r-modalthreeddd-editor@onekstar/store@infinitebrahmanuniverse/nolb-zu@mr-quin/bilibili-api@product360/editor@quantform/studio@remix-kawaii/language@lvl-up/lib-react@canaan_run/canaanfarshid-kit-test-123@remix-kawaii/zustandlucents-talks-pluginstrapionsubra-components@strapion/core@textea/yodasmui-react-chatmui-chat-widgetmichaelangrivera-ts-restpiing-gatekeeper-v2-clientweread-scraperr-modal-storeecommerce4all-nextecommerce4all-ui-testnmc-components-ecommerce@everything-registry/sub-chunk-3242dinglo@quik-ui/react@partykit/site-new@interplanetary-share/hooks.indexdb@interplanetary-share/hooks.ipfs-client@intershare/hooks.ipfs-clienttest-ecommerce-al@clarktotoro/toastcmp-4allhackhubb-components@marcoarnulfo/react-typescriptodinkitdinglo-io1dinglo-io2dinglo-io3dinglo-io4dinglo-io5dinglo-io6dinglo-io7dappkit-react@ihezebin/doraemonnoot-cosmeticsghosty-widget@freeofcode/events@freeofcode/hookscra-template-defaultreactthree-coreteste-lib-gabrielradmat-united-appshopify-connect-wallet-deputytest-zustify
5.0.0-alpha.6

1 month ago

5.0.0-alpha.5

2 months ago

5.0.0-alpha.4

2 months ago

4.5.2

2 months ago

5.0.0-alpha.3

2 months ago

4.5.1

2 months ago

5.0.0-alpha.2

3 months ago

5.0.0-alpha.1

3 months ago

5.0.0-alpha.0

3 months ago

4.5.0

3 months ago

4.4.5

6 months ago

4.4.7

5 months ago

4.4.6

6 months ago

4.4.4

6 months ago

4.4.1

9 months ago

4.4.0

9 months ago

4.4.3

7 months ago

4.4.2

7 months ago

4.3.9

10 months ago

4.3.4

1 year ago

4.3.3

1 year ago

4.3.6

1 year ago

4.3.5

1 year ago

4.3.8

12 months ago

4.3.7

1 year ago

4.3.2

1 year ago

4.3.1

1 year ago

4.3.0

1 year ago

4.2.0

1 year ago

4.1.4

1 year ago

4.1.3

2 years ago

4.1.5

1 year ago

4.1.2

2 years ago

4.0.0

2 years ago

4.0.0-rc.3

2 years ago

4.0.0-rc.2

2 years ago

4.0.0-rc.4

2 years ago

4.1.0

2 years ago

4.1.1

2 years ago

4.0.0-rc.1

2 years ago

3.7.2

2 years ago

4.0.0-rc.0

2 years ago

4.0.0-beta.3

2 years ago

3.7.1

2 years ago

4.0.0-beta.2

2 years ago

3.6.9

2 years ago

3.6.8

2 years ago

3.6.7

2 years ago

3.7.0

2 years ago

4.0.0-beta.1

2 years ago

4.0.0-alpha.7

2 years ago

3.6.6

2 years ago

3.6.5

2 years ago

4.0.0-beta.0

2 years ago

4.0.0-alpha.5

2 years ago

4.0.0-alpha.6

2 years ago

3.6.2

2 years ago

3.6.4

2 years ago

3.6.3

2 years ago

3.6.1

3 years ago

3.5.14

3 years ago

3.5.13

3 years ago

3.6.0

3 years ago

4.0.0-alpha.4

3 years ago

3.5.12

3 years ago

3.5.11

3 years ago

4.0.0-alpha.3

3 years ago

4.0.0-alpha.2

3 years ago

3.5.10

3 years ago

4.0.0-alpha.1

3 years ago

3.5.9

3 years ago

3.5.8

3 years ago

3.5.7

3 years ago

3.5.6

3 years ago

3.5.5

3 years ago

3.5.4

3 years ago

3.5.3

3 years ago

4.0.0-alpha.0

3 years ago

3.5.2

3 years ago

3.4.2

3 years ago

3.5.1

3 years ago

3.5.0

3 years ago

3.4.1

3 years ago

3.4.0

3 years ago

3.3.3

3 years ago

3.3.2

3 years ago

3.3.1

3 years ago

3.3.0

3 years ago

3.2.0

3 years ago

3.1.4

3 years ago

3.1.3

4 years ago

3.1.2

4 years ago

3.1.1

4 years ago

3.1.0-beta.0

4 years ago

3.1.0

4 years ago

3.0.3

4 years ago

3.0.2

4 years ago

3.0.1

4 years ago

3.0.0

4 years ago

3.0.0-beta.0

4 years ago

2.2.4

4 years ago

2.2.3

4 years ago

2.2.2

4 years ago

2.2.1

4 years ago

2.2.0

5 years ago

2.1.0

5 years ago

2.0.0

5 years ago

1.0.7

5 years ago

1.0.6

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.3

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago

1.0.0-beta.1

5 years ago

1.0.0-beta.0

5 years ago

0.2.2-beta.0

5 years ago

0.2.2

5 years ago

0.2.1

5 years ago

0.2.0

5 years ago

0.1.3

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago

0.0.6

5 years ago

0.0.5

5 years ago

0.0.4

5 years ago

0.0.3

5 years ago

0.0.2

5 years ago

0.0.1

5 years ago