Schnellstart

Installation

npm
pnpm
yarn
bun
npm install @violetflux/kerros

Kerros funktioniert mit npm, pnpm, Yarn und Bun und unterstützt React 17, 18 und 19.

Einen Store erstellen

Jeder custom Hook kann mit createStore zu einem zwischen Komponenten geteilten Store werden.

import { createStore } from '@violetflux/kerros'
import { useState } from 'react'

interface Task {
  id: string
  title: string
}

function useTaskStoreValue() {
  const [tasks, setTasks] = useState<Task[]>([])

  const addTask = (task: Task) => {
    setTasks(v => [...v, task])
  }

  const finishTask = (taskId: string) => {
    setTasks(v => v.filter(task => task.id !== taskId))
  }

  return { tasks, addTask, finishTask }
}

export const [useTask, TaskProvider] = createStore(useTaskStoreValue)

createStore gibt den Hook für Komponenten und den dazugehörigen Provider zurück. Im Store kannst du weiterhin useState, useReducer, Context, SDK Hooks und eigene Hooks verwenden.

Verwende für den Initializer einen benannten Top-Level-Hook wie useTaskStoreValue. Anonyme Initializer funktionieren weiterhin, werden vom React Compiler im infer-Modus aber nicht automatisch als Hooks kompiliert.

Provider einbinden

useTask darf nur unterhalb von TaskProvider aufgerufen werden.

function App() {
  return (
    <TaskProvider>
      <Header />
      <TaskList />
    </TaskProvider>
  )
}

Außerhalb des Providers wirft Kerros einen verständlichen Fehler.

Store in einer Komponente verwenden

Der Selektor gibt nur die Felder zurück, welche die Komponente tatsächlich nutzt.

function TaskList() {
  const { tasks, finishTask } = useTask(s => ({
    tasks: s.tasks,
    finishTask: s.finishTask,
  }))

  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          {task.title}
          <button onClick={() => finishTask(task.id)}>Erledigt</button>
        </li>
      ))}
    </ul>
  )
}

Kerros vergleicht die obersten Felder des Selektor-Objekts flach. Solange diese ausgewählten Felder gleich bleiben, rendern andere Store-Updates TaskList nicht neu. Der Selektor darf inline stehen und braucht kein useCallback.

Mehrere Store-Instanzen

Jeder eingebundene Provider erstellt einen unabhängigen Store.

<TaskProvider>
  <h2>Private Aufgaben</h2>
  <TaskList />
</TaskProvider>

<TaskProvider>
  <h2>Teamaufgaben</h2>
  <TaskList />
</TaskProvider>

Jede TaskList liest automatisch den nächstgelegenen Provider. Die Daten bleiben getrennt.

Abhängigkeiten zwischen Stores

Ein Store kann einen anderen Store direkt aufrufen.

function useTaskStoreValue() {
  const { user } = useAccount(s => ({ user: s.user }))
  const [tasks, setTasks] = useState<Task[]>([])

  const addTask = (title: string) => {
    if (!user)
      return

    setTasks(v => [...v, {
      id: crypto.randomUUID(),
      title,
      assigneeId: user.id,
    }])
  }

  return { tasks, addTask }
}

export const [useTask, TaskProvider] = createStore(useTaskStoreValue)

Der Provider der Abhängigkeit liegt außen. Halte die Abhängigkeit einseitig.

<AccountProvider>
  <TaskProvider>
    <App />
  </TaskProvider>
</AccountProvider>

Props an den Provider übergeben

Provider props werden an den Store Hook weitergegeben.

function useCounterStoreValue({ initialCount }: { initialCount: number }) {
  const [count, setCount] = useState(initialCount)
  return { count, setCount }
}

const [useCounter, CounterProvider] = createStore(useCounterStoreValue)
<CounterProvider initialCount={42}>
  <Counter />
</CounterProvider>

Ausprobieren

Dieser Zähler verwendet dieselbe API aus createStore + Provider + selector.

Weiter geht es mit Selektoren und Store-Komposition.