Skip to content
/ zustand Public
forked from pmndrs/zustand

🐻 Bear necessities for state management in React

License

Notifications You must be signed in to change notification settings

dm385/zustand

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

89 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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, not context based (no reliance on providers, breaches reconciler boundaries), and is cross-platform to boot. Make your paws dirty with a small live demo here.

Create a store (or multiple, up to you...)

You could be in global or component scope, manage your store anywhere you want!

import create from 'zustand'

// Name your store anything you like, but remember, it's a hook!
const [useStore] = create(set => ({
  // Everything in here is your state
  count: 1,
  // You don't have to nest your actions, but makes it easier to fetch them later on
  actions: {
    inc: () => set(state => ({ count: state.count + 1 })), // same semantics as setState
    dec: () => set(state => ({ count: state.count - 1 })),
  },
}))

Bind components

Look Ma, no providers!

function Counter() {
  // Will only re-render the component when "count" changes
  const count = useStore(state => state.count)
  return <h1>{count}</h1>
}

function Controls() {
  // "actions" isn't special, we just named it like that to fetch updaters easier
  const { inc, dec } = useStore(state => state.actions)
  return (
    <>
      <button onClick={inc}>up</button>
      <button onClick={dec}>down</button>
    </>
  )
}

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

It's just like mapStateToProps in Redux. zustand will run a small shallow equal over the object you return. Of course, it won't cause re-renders if these properties aren't changed in the state model.

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

Or, if you prefer, 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 a result into another selector is straight forward.

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

Memoizing selectors, optimizing performance

Flux stores usually call the selector on every render-pass. Most of the time this isn't much of a problem, but when your selectors are computationally expensive, or when you know the component renders a lot (for instance react-motion calling it 60 times per second for animation purposes) you may want to optimize it.

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

In this case the selector state => state.foo[props.id] will run on every state change, as well as every time the component renders. This isn't expensive at all, 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 updates:

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

From there on your selector will only run when either 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 => ({
  result: '',
  fetch: async url => {
    const response = await fetch(url)
    const json = await response.json()
    set({ result: json })
  },
}))

Read from state in actions

The set function already allows functional update set(state => result) but should there be cases where you need to access outside of it you have an optional get, too.

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

Sick of reducers and changing nested state? Use Immer!

Having to build nested structures bearhanded is one of the more tiresome aspects of reducing state. Have you tried immer? It is a tiny package that allows you to work with immutable state in a more convenient way. You can easily extend your store with it.

import produce from "immer"

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

const set = useStore(state => state.set)
set(draft => {
  draft.nested.structure.contains.a.value = false
  draft.nested.structure.contains.anotherValue = true
})

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

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

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

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

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

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({ n: 0 })

// Getting fresh state
const n = api.getState().n
// Listening to changes
const unsub = api.subscribe(state => console.log(state.n))
// Updating state, will trigger listeners
api.setState({ n: 1 })
// Unsubscribing handler
unsub()
// Destroying the store
api.destroy()

Middleware

const logger = fn => (set, get) => fn(args => {
  console.log("  applying", args)
  set(args)
  console.log("  new state", get())
}, get)

const [useStore] = create(logger(set => ({
  text: "hello",
  setText: text => set({ text })
})))

About

🐻 Bear necessities for state management in React

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 81.2%
  • JavaScript 18.8%