github.com-react-spring-zustand_-_2019-12-02_06-36-56

github.com-react-spring-zustand_-_2019-12-02_06-36-56

Posted by

Download
ITEM TILE – File Size: 7.1K

🐻 Bear necessities for state management in React

bear.png

Bundle SizeBuild Status npm version npm

Small, fast and scaleable bearbones state-management solution. Has a comfy api based on hooks, that isn’t 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.

“`jsximport 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.

“`jsxfunction Counter() { const count = useStore(state => state.count) return

{count}

}

function Controls() { const increase = useStore(state => state.increase) return }“`

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!

jsxconst state = useStore()

Selecting multiple state slices

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

jsxconst 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.

“`jsximport shallow from ‘zustand/shallow’

// Object pick, re-renders the component when either foo or bar changeconst { foo, bar } = useStore(state => ({ foo: state.foo, bar: state.bar }), shallow)

// Array pick, re-renders the component when either foo or bar changeconst [foo, bar] = useStore(state => [state.foo, state.bar], shallow)

// Mapped picks, re-renders the component when state.objects changes in order, count or keysconst keys = useStore(state => Object.keys(state.objects), 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.

jsxconst 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.

jsconst 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.

jsxconst [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.

jsxconst [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?

“`jsximport 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.

“`jsxconst [, api] = create(() => ({ a: 1, b: 2, c: 3 }))

// Getting fresh stateconst a = api.getState().a// Listening to all changes, fires on every dispatchconst unsub1 = api.subscribe(state => console.log(“state changed”, state))// Listening to selected changesconst unsub2 = api.subscribe(a => console.log(“a changed”, a), state => state.a)// Updating state, will trigger listenersapi.setState({ a: 1 })// Unsubscribe listenersunsub1()unsub2()// Destroying the store (removing all listeners)api.destroy()“`

Transient updates (for often occuring state-changes)

The api signature of subscribe(callback, selector):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 for automatic unsubscribe on unmount. This can make a drastic performance difference when you are allowed to mutate the view directly.

“`jsxconst [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), state => state[id]), [id])“`

Middleware

You can functionally compose your store any way you like.

“`jsx// Log every time state is changedconst 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 proxyconst 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?

“`jsxconst 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.

“`jsximport { redux } from ‘zustand/middleware’

const [useStore] = create(redux(reducer, initialState))“`

Redux devtools

“`jsximport { 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 typesconst [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.

To restore the repository download the bundle

wget https://archive.org/download/github.com-react-spring-zustand_-_2019-12-02_06-36-56/react-spring-zustand_-_2019-12-02_06-36-56.bundle

and run:

 git clone react-spring-zustand_-_2019-12-02_06-36-56.bundle 

Source: https://github.com/react-spring/zustand
Uploader: react-spring
Upload date: 2019-12-02