Getting Started
Introduction
A simple and easy-to-use state manager that utilizes nanostores as its core implementation.
You can think of @codeleap/store as:
nanostoresunderneath- plus selector-based React consumption
- plus ergonomic object updates, optional persistence, and array-store helpers
Installing the library
Install @codeleap/store through bun:
bun add @codeleap/store
Store vs Query — When to use each
This is the most common source of confusion. The rule is simple:
| Situation | Use |
|---|---|
| UI state (sidebar open, selected tab, theme) | @codeleap/store |
| Feature flags, user preferences | @codeleap/store |
| Data that lives only on the client | @codeleap/store |
| Data that comes from an API | @codeleap/query |
| Data that multiple components need to refetch | @codeleap/query |
| Lists, pagination, cache invalidation | @codeleap/query |
Never use store to cache server responses. It has no concept of staleness, background refetch, or cache invalidation — that's what @codeleap/query is built for.
// ✅ Correct — UI state in store
const sidebarState = globalState({ isOpen: false })
// ✅ Correct — server data in query
const usersManager = createQueryManager({ name: 'users', ... })
// ❌ Wrong — don't fetch and store API data manually
const usersState = globalState([])
useEffect(() => {
fetch('/api/users').then(r => r.json()).then(data => usersState.set(data))
}, [])
Setup
Import globalState from the library and define your state:
import { globalState } from '@codeleap/store'
const initialState = {
modal1: false,
modal2: false,
}
export const state = globalState(initialState)
export function toggleModal(modal: keyof typeof initialState, value?: boolean) {
const newValue = typeof value === 'boolean' ? value : !state.value[modal]
state.set({ [modal]: newValue })
}
For real app usage, prefer a small store module with exported helper functions instead of scattering writes across components:
import { globalState } from '@codeleap/store'
export const state = globalState({
authFinished: false,
hasResolvedInitialAuth: false,
})
export function setHasResolvedInitialAuth(hasResolvedInitialAuth: boolean) {
state.set({ hasResolvedInitialAuth })
}
export function setAuthFinished(authFinished: boolean) {
state.set({ authFinished })
}
export function useAuthFinished() {
return state.use(s => s.authFinished)
}
Important behavior
These rules are the ones people most often misunderstand:
set(...) merges object state
For object stores, set(...) merges the partial value into the current object:
const state = globalState({
authFinished: false,
hasResolvedInitialAuth: false,
})
state.set({ authFinished: true })
// result:
// {
// authFinished: true,
// hasResolvedInitialAuth: false,
// }
This is why state.set({ someKey: value }) is the normal way to update one key in an object store.
reset(...) replaces the whole value
reset(...) is the raw underlying store set, so it replaces the full value exactly.
Use it only when full replacement is what you actually want.
state.reset({
authFinished: false,
hasResolvedInitialAuth: false,
})
Do not describe reset(...) as a magical "restore the initial state" helper. It only sets the full value you pass to it.
Prefer selectors for object stores
When a component only needs one field from an object store, prefer:
const authFinished = state.use(s => s.authFinished)
instead of:
const fullState = state.use()
That keeps rerenders scoped to the selected value instead of the whole object.
Array methods only work on array stores
If the store value itself is an array, the store exposes array methods such as push, unshift, map, and filter.
const itemsState = globalState([] as string[])
itemsState.push('hello')
itemsState.unshift('first')
const upper = itemsState.map(item => item.toUpperCase())
Those methods are only available when the store value is actually an array. Calling array methods on a non-array store is invalid.
Persistence is local-device persistence
Use persistKey for client-owned local state such as:
- user preferences
- remembered toggles
- onboarding progress
- UI state you want to restore on the same device
Do not treat persistKey as:
- server sync
- shared multi-device persistence
- a replacement for API cache