@sinakhx/use-zustand-store v0.3.0
@sinakhx/useZustandStore
custom helpers for using zustand in react apps. it can be used for creating local (component scoped) stores using Zustand. So that:
- you won't need to worry about garbage collecting your store on page components' unmount lifecycle.
- you can get rid of using multiple selectors to acces different parts of the store (as it's using react-tracked under the hood)
- you avoid making your codebase weird with currying, Providers, mind-boggling type annotations, etc.
Installation
npm install @sinakhx/use-zustand-storeUsage
Creating a store is exactly the same way as creating a store in Zustand. You only need to change Zustand's create function with this library's createZustandStore function. Everything else is the same. (It's just a wrapper to avoid nesting due to currying)
Example counter app:
counterStore.ts
import { createZustandStore, mutateStoreItem } from '@sinakhx/use-zustand-store'
interface ICounterStore {
count: number
increment: () => void
}
export const counterStore = createZustandStore<ICounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))CounterComponent.tsx
import { useZustandStore } from '@sinakhx/use-zustand-store'
import { counterStore } from './counterStore'
const CounterComponent = () => {
const store = useZustandStore(counterStore)
return <button onClick={store.increment}>{store.count}</button>
}
export default CounterComponentNow the store is bound to the component. By changing the page route (unmounting the component), the store gets garbage collected & by going back to the page (mounting the component again), a fresh store is created.
That's done! Happy coding!
Instead of using Immer or nested destructuring to mutate the store, you can use the mutateStoreItem helper.
The following example demonstrates how to reduce multiple useState hooks to a single store.
tableStore.ts
import { createZustandStore, mutateStoreItem } from '@sinakhx/use-zustand-store'
type TableRow = {
id: number
name: string
age: number
}
interface ITableStore {
rows: Array<TableRow>
setRows: (rows: TableRow[]) => void
selectedRow: TableRow | null
setSelectedRow: (row: TableRow | null) => void
handleDeleteRow: (id: number) => void
}
const counterStore = createZustandStore<ITableStore>((set, get) => ({
rows: [],
setRows: (rows) => set(mutateStoreItem({ rows })),
selectedRow: null,
setSelectedRow: (row) => set(mutateStoreItem({ selectedRow: row })),
handleDeleteRow: (id) => {
const newRows = get().rows.filter((row) => row.id !== id)
get().setRows(newRows)
},
}))mutateStoreItem is using optics-ts to access the store's state. As a result one can also easily mutate a nested store item by providing its path as object key. e.g: set(mutateStoreItem({ 'user.info.name': 'John' })).
counterStore.ts
import { createZustandStore } from '@sinakhx/use-zustand-store'
interface ICounterStore {
count: number
increment: () => void
}
interface ICounterProps {
initialCount: number
}
export const counterStoreFactory = ({ initialCount } : ICounterProps) => createZustandStore<ICounterStore>((set) => ({
count: initialCount,
increment: () => set((state) => ({ count: state.count + 1 })),
}))CounterComponent.tsx
import { useZustandStore } from '@sinakhx/use-zustand-store'
import { counterStoreFactory } from './counterStore'
interface ICounterProps {
initialCount: number
}
const CounterComponent = ({ initialCount }: ICounterProps) => {
const store = useZustandStore(counterStoreFactory({ initialCount }))
return <button onClick={store.increment}>{store.count}</button>
}
export default CounterComponentIn that case, you can create a global version of the useZustandStore hook by using the createTrackedSelector helper from react-tracked
counterStore.ts
import { createZustandStore, createTrackedSelector } from '@sinakhx/use-zustand-store'
interface ICounterStore {
count: number
increment: () => void
}
const counterStore = createZustandStore<ICounterStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
export const useGlobalCounterStore = createTrackedSelector(counterStore())CounterComponent.tsx
// import { useZustandStore } from '@sinakhx/use-zustand-store'
import { useGlobalCounterStore } from './counterStore'
const CounterComponent = () => {
const store = useGlobalCounterStore()
return <button onClick={store.increment}>{store.count}</button>
}
export default CounterComponentnow the store is independent from the components & will keep its state regardless of the route changes.
Contributing
Please feel free to open an issue or create a pull request to add a new feature or fix a bug. (see contributing for more details)
License
The MIT License (MIT)
© 2022 Sina Khodabandehloo