Skip to main content

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:

  • nanostores underneath
  • 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:

SituationUse
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:

src/stores/example.ts
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:

src/stores/auth.ts
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