state

Keep app-wide values like a cart or settings, and optionally save them in the browser.

App stateClientBuilt onZustand
/play/state/persist-cart
Add items, then press Reload. The cart comes back from localStorage.

What it is

@kitr/state is Zustand with one helper, createKitrStore. Define the state and its actions; components select the slice they need and re-render only when it changes. Ask for persistence and the store saves itself, with versions for old shapes.

Runs in the browser and during server rendering. For data a server owns, use query.

Install

kitr add state

First time? Run npm i -g @kitr/kitr once. Inside a project, the global kitr runs your project's own version.

Adds @kitr/state and zustand, and creates app/stores/. No provider, no config file. Every change.

Quick start

Put each store in app/stores/. persist: true keeps it across reloads:

// app/stores/order-draft.ts
import { createKitrStore } from '@kitr/state'

export interface DraftLine {
  sku: string
  name: string
  price: number
  quantity: number
}

interface OrderDraftState {
  lines: DraftLine[]
  addLine: (line: Omit<DraftLine, 'quantity'>) => void
  clear: () => void
}

export const [useOrderDraft, OrderDraftProvider, orderDraftStore] =
  createKitrStore<OrderDraftState>({ name: 'order-draft', persist: true }, (set) => ({
    lines: [],
    addLine: (line) =>
      set((state) => {
        const existing = state.lines.find((l) => l.sku === line.sku)
        if (!existing) return { lines: [...state.lines, { ...line, quantity: 1 }] }
        return {
          lines: state.lines.map((l) => (l.sku === line.sku ? { ...l, quantity: l.quantity + 1 } : l)),
        }
      }),
    clear: () => set({ lines: [] }),
  }))

Then use it from any two components, however far apart. Each selects only what it needs, so it re-renders only when that changes:

// app/pages/orders/new.tsx
import { useOrderDraft } from '#app/stores/order-draft'

function AddSeat() {
  const addLine = useOrderDraft((s) => s.addLine)
  return (
    <button type="button" onClick={() => addLine({ sku: 'acme-seat', name: 'Acme seat', price: 12 })}>
      Add a seat
    </button>
  )
}

function DraftCount() {
  const count = useOrderDraft((s) => s.lines.reduce((sum, l) => sum + l.quantity, 0))
  return <p>{count} items in this order</p>
}

export default function NewOrderPage() {
  return (
    <main>
      <DraftCount />
      <AddSeat />
    </main>
  )
}

Press Add a seat three times: 3 items in this order. Reload and it still reads 3, from localStorage. kitr make:store <name> writes a starter store. Next in the guide: Create your first store.

Built on

kitr adds createKitrStore: safe server rendering, persistence with versions, Redux DevTools and seeding from a loader.

Problems it solves

Further reading