zustand-sliced
v0.2.0
Published
Namespaced slices for zustand — scoped set, full-store get, zero boilerplate
Downloads
26
Maintainers
Readme
zustand-sliced
Namespaced slices for zustand. Fully typed, works with all middleware (immer, devtools, persist, ...).
Why
Zustand's slice pattern flat-merges all state into one object:
const useStore = create((...a) => ({
...createAuthSlice(...a),
...createCartSlice(...a),
}))
useStore(s => s.user) // which slice?
useStore(s => s.items) // name collision if both slices have `items`You end up prefixing everything (authUser, cartItems, authLoading, cartLoading, ...) which defeats the purpose of slices.
Usage
npm install zustand-slicedDefine your store shape as interfaces, use sliced() with zustand's create():
import { create } from 'zustand'
import { sliced } from 'zustand-sliced'
interface AuthSlice {
user: string | null
login: (name: string) => void
}
interface CartSlice {
items: string[]
add: (item: string) => void
}
interface Store {
auth: AuthSlice
cart: CartSlice
}
const useStore = create<Store>()(
sliced({
auth: (set, get) => ({
user: null,
login: (name) => set({ user: name }),
// set({ typo: 1 }) — compile error
}),
cart: (set, get) => ({
items: [],
add: (item) => set(s => ({ items: [...s.items, item] })),
// get().auth.user — fully typed cross-slice read
}),
})
)
// Namespaced, fully typed
useStore(s => s.auth.user) // string | null
useStore(s => s.cart.items) // string[]setis scoped to its own slice and fully typed —set({ user: name })only touchesstate.authgetreturns the full store, fully typed —get().cart.itemsfor cross-slice reads- Missing or wrong slice properties are caught at compile time
Slices can live in separate files:
// store.ts
import { authSlice } from './features/auth/slice'
import { cartSlice } from './features/cart/slice'
export const useStore = create<Store>()(
sliced({ auth: authSlice, cart: cartSlice })
)Middleware
sliced() returns a standard zustand StateCreator, so all middleware works exactly like normal zustand — just wrap it:
import { devtools, persist, createJSONStorage } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'
const useStore = create<Store>()(
devtools(
persist(
immer(
sliced({
auth: (set, get) => ({
user: null,
login: (name) => set(s => { s.user = name }), // immer draft mutation
}),
cart: (set, get) => ({
items: [],
add: (item) => set(s => { s.items.push(item) }), // just push
}),
})
),
{ name: 'store', storage: createJSONStorage(() => localStorage) }
)
)
)| Middleware | Support |
|---|---|
| immer | Draft mutations in set |
| devtools | Actions auto-labeled as auth/set, cart/set |
| persist | Namespaced state serialized as-is |
| Any other | Standard StateCreator — just works |
License
MIT
