In another epiphany of the morning of this December 31, I enhanced my previous utility function to create Zustand stores, including devtools
and immer
middlewares:
import {create, StateCreator, StoreApi, UseBoundStore} from 'zustand'; import {devtools} from 'zustand/middleware'; import {immer} from 'zustand/middleware/immer'; type ZustandActions<T extends object, U extends object> = StateCreator< T, [['zustand/devtools', never], ['zustand/immer', never]], [['zustand/immer', never], ['zustand/devtools', never]], U >; /** * Creates a Zustand store with state, computed and actions. */ export function createStore< T extends object, U extends object, C extends Record<string, (s: UseBoundStore<StoreApi<T>>) => void>, > ( { state, computed = {} as C, actions = {} as ZustandActions<T, U>, }: { state: T; computed?: C; actions?: ZustandActions<T, U>; }, ) { const store = create( devtools( immer(() => state), {name: Math.random().toString(36).slice(2), serialize: true}, ), ); if (import.meta.env.DEV) { for (const name in computed) { if (!name.startsWith('use')) throw new Error(name + ' is a hook, and its name must start with the word "use".'); } } return { use: store, ...computed, ...actions(store.setState, store.getState, store), }; }
The key improvement here is that computed
functions will be 100% reactive, because they’re simple hooks attached to the store object – they work like ordinary hook functions:
const bearStore = createStore({ state: { bears: 0, }, computed: { useHasBears: () => bearStore.use(s => s.bears > 0), }, actions: (set, get) => ({ increasePopulation(): void { set(state => { ++state.bears; }); }, }), }); function App() { const bears = bearStore.use(s => s.bears); const hasBears = bearStore.useHasBears(); return <div> <span>{bears} - {hasBears}</span> <button onClick={() => bearStore.increasePopulation()}> Change </button> </div>; }
I’m in awe looking at such a small function yielding such a great usability.
No comments:
Post a Comment