Skip to content

oneirosoft/quiddity

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@oneirosoft/quiddity

npm npm downloads types license bundle size CI Publish

Local-first React state helpers that keep the API small and the updates clear.

📦 Install

bun add @oneirosoft/quiddity

or

npm install @oneirosoft/quiddity

🧠 Core Idea

create builds a local store hook. Each component that calls the hook gets its own isolated state and actions. Updates are always partial merges of state and are driven through a set function you define inside your actions.

🚀 Quick Start

import { create } from "@oneirosoft/quiddity"

const useCounter = create((set) => ({
  count: 0,
  label: "Clicks",
  inc: (by = 1) => set((state) => ({ count: state.count + by })),
  setLabel: (label: string) => set({ label }),
}))

export function Counter() {
  const store = useCounter()

  return (
    <button onClick={() => store.inc()}>
      {store.label}: {store.count}
    </button>
  )
}

TypeScript tip:

type CounterStore = {
  count: number
  label: string
  inc: (by?: number) => void
  setLabel: (label: string) => void
}

const useCounter = create<CounterStore>((set) => ({
  count: 0,
  label: "Clicks",
  inc: (by = 1) => set((state) => ({ count: state.count + by })),
  setLabel: (label) => set({ label }),
}))

combine helper:

import { combine, create } from "@oneirosoft/quiddity"

const useCounter = create(
  combine({ count: 0 }, (set) => ({
    inc: () => set((state) => ({ count: state.count + 1 })),
    setCount: (count: number) => set({ count }),
  }))
)

Use combine when you want a clean separation between plain initial state and action creators, while still getting good inference in the builder. It keeps your initial state visible and avoids repeating fields inside actions.

🧩 Object-Only Store (state + actions together)

import { create } from "@oneirosoft/quiddity"

const useToggle = create((set) => ({
  on: false,
  toggle: () => set((state) => ({ on: !state.on })),
}))

export function Toggle() {
  const store = useToggle()
  return <button onClick={store.toggle}>{store.on ? "On" : "Off"}</button>
}

🧰 API Shape

create(builder)
create(builder, derive?)

// builder signature
type Builder<S> = (set: (update: Partial<S> | ((s: S) => Partial<S>)) => void) => S

// optional derive signature
type Derive<S, D> = (state: S) => D

create returns a hook:

const useStore = create(...)
const store = useStore()

store is the merged object of:

  • your actions
  • current state values

🧪 How Updates Work

  • set accepts a partial object or an updater function.
  • Updates are merged into current state (shallow merge).
  • Only non-function keys are considered state. Functions are treated as actions.
  • State, action, and derived keys must be unique. Overlaps are rejected by TypeScript and also throw at runtime.

🧮 Derived Values

const useCounter = create(
  (set) => ({
    count: 0,
    inc: () => set((s) => ({ count: s.count + 1 })),
  }),
  (state) => ({ doubleCount: state.count * 2 })
)

const store = useCounter()
// store.doubleCount is a derived value

Derived values are computed from state on render and are read-only. They update whenever the underlying state changes, but they do not participate in set updates directly.

You can also return functions from derive:

const useMath = create(
  (set) => ({
    count: 0,
    inc: () => set((s) => ({ count: s.count + 1 })),
  }),
  (state) => ({
    doubleCount: state.count * 2,
    multBy: (n: number) => state.count * n,
  })
)

const store = useMath()
store.multBy(3) // uses the latest state

🎯 Rendering Behavior (important)

  • The store is local to each component instance.
  • Any state update triggers a re-render of the component using the hook.
  • Destructuring fewer fields does not avoid re-renders.
  • To isolate re-renders, split state into multiple hooks or components.

✅ Usage Notes

  • Keep state serializable (it is read via object entries).
  • Actions can call set multiple times; updates are merged in order.
  • This library is intentionally minimal and does not provide global stores.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published