🔔 Alert..!! Get 2 Month Free Cloud Hosting With $200 Bonus From Digital Ocean ACTIVATE DEAL

Small, fast and scaleable barebones state-management solution. 

Others React

Documentation

Build Status npm version npm

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.

npm install zustand 

First create a store

Your store is a hook! You can put anything in it, atomics, objects, functions. Like Reacts setState, set merges state.

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

Then bind your components, 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?

  • Simpler and un-opinionated
  • Makes hooks the primary means of consuming state
  • Doesn't wrap your app into context providers
  • Can inform components transiently (without causing render)

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

zustand defaults to strict-equality (old === new) to detect changes, this is efficient for atomic state picks.

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

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 an alternative equality function.

import shallow from 'zustand/shallow'  const { foo, bar } = useStore(state => ({ foo: state.foo, bar: state.bar }), 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.

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

Memoizing selectors

Selectors run on state changes, as well as when the component renders. If you give zustand a fixed reference it will only run on state changes, or when the selector changes. Don't worry about this, unless your selector is expensive.

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

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)

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(a => console.log("a changed", a), {   selector: state => state.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(coords =>     (xy.current = coords), { selector: state => state[id] }), [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   }) }))))

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 })

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. Try this example.

import { redux } from 'zustand/middleware'  const [useStore] = create(redux(reducer, initialState))

Redux devtools

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)))

devtools takes the store function as its first argument, optionally you can name the store with a second argument: devtools(store, "MyStore"), which will be prefixed to your actions.


You May Also Like