Démarrage rapide

Installation

npm
pnpm
yarn
bun
npm install @violetflux/kerros

Kerros fonctionne avec npm, pnpm, Yarn et Bun, et prend en charge React 17, 18 et 19.

Créer un Store

N'importe quel custom Hook peut devenir un état partagé grâce à createStore.

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 renvoie le Hook utilisé par les composants et son Provider. Le Store peut toujours utiliser useState, useReducer, Context, des Hooks SDK ou vos propres Hooks.

Conservez l'initializer sous la forme d'un Hook nommé au niveau du module, comme useTaskStoreValue. Les initializers anonymes continuent de fonctionner, mais React Compiler ne les compile pas automatiquement comme Hooks en mode infer.

Monter le Provider

useTask ne peut être appelé que dans les descendants de TaskProvider.

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

En dehors du Provider, Kerros lève une erreur explicite.

Utiliser le Store dans un composant

Le sélecteur renvoie uniquement les champs utilisés par le composant.

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)}>Terminer</button>
        </li>
      ))}
    </ul>
  )
}

Kerros compare superficiellement les champs de premier niveau de l'objet. Tant que ces champs sélectionnés restent égaux, les autres mises à jour du Store ne rendent pas TaskList à nouveau. Le sélecteur peut rester en ligne et n'a pas besoin de useCallback.

Plusieurs instances

Chaque Provider monté crée un Store indépendant.

<TaskProvider>
  <h2>Tâches personnelles</h2>
  <TaskList />
</TaskProvider>

<TaskProvider>
  <h2>Tâches de l'équipe</h2>
  <TaskList />
</TaskProvider>

Chaque TaskList lit automatiquement le Provider le plus proche. Les données restent isolées.

Dépendances entre Stores

Un Store peut appeler un autre Store directement.

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)

Montez le Provider de la dépendance à l'extérieur et gardez une dépendance unidirectionnelle.

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

Passer des props au Provider

Les props du Provider sont transmises au Hook du Store.

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

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

Essayer

Ce compteur utilise la même API createStore + Provider + selector.

Continuez avec les Sélecteurs et la Composition.