Skip to content

@paretojs/core/store

Built-in state management powered by Immer. See State Management for concepts, guidance on when to use global vs. context stores, and performance tips.

import { defineStore, defineContextStore } from '@paretojs/core/store'

Create a global reactive store. Supports direct destructuring. The initializer receives set for Immer-powered state updates and get for reading current state:

const counterStore = defineStore((set, get) => ({
count: 0,
increment: () => set((draft) => { draft.count++ }),
double: () => set((draft) => { draft.count = get().count * 2 }),
}))
// Usage
const { count, increment } = counterStore.useStore()
PropertyTypeDescription
useStore()() => StateReact hook — re-renders on state change
getState()() => StateGet current state outside React
setState(fn)(fn: (draft) => void) => voidUpdate state with Immer draft
subscribe(fn)(fn: (state, prevState) => void) => () => voidListen for changes, returns unsubscribe

Create a per-instance store with React context. SSR-safe (no shared global state between requests). Use this when the store holds per-request data like the current user or auth tokens. See State Management — When to use global vs. context stores for guidance.

const { Provider, useStore } = defineContextStore((initial: { theme: string }) => (set) => ({
theme: initial.theme,
toggle: () => set((d) => { d.theme = d.theme === 'light' ? 'dark' : 'light' }),
}))
// Wrap in Provider with initialData
<Provider initialData={{ theme: 'light' }}>
<App />
</Provider>
// Use in child components
const { theme, toggle } = useStore()
PropertyTypeDescription
ProviderReact.FC<{ children: ReactNode; initialData: Init }>Context provider — wrap your component tree
useStore()() => StateReact hook — reads from the nearest Provider

The set function receives an Immer draft — you can mutate it directly:

set((draft) => {
draft.items.push(newItem) // push to array
draft.count++ // increment
delete draft.temp // delete property
draft.nested.value = 'new' // deep mutation
})

Immer ensures immutability under the hood. Each set() call produces a new state object, which triggers re-renders in components that use the store. You never need to spread or clone state manually.

Use defineContextStore to hydrate a store from server data. Pass loader data to <Provider initialData={data}>:

const { Provider, useStore } = defineContextStore((data) => (set) => ({
count: data.count,
increment: () => set((d) => { d.count++ }),
}))
export function loader() {
return { count: 10 }
}
export default function Page() {
const data = useLoaderData()
return (
<Provider initialData={data}>
<Counter />
</Provider>
)
}

See a full example at examples/app/ssr-store/page.tsx.