Inicio rápido

Instalación

npm
pnpm
yarn
bun
npm install @violetflux/kerros

Kerros funciona con npm, pnpm, Yarn y Bun, y es compatible con React 17, 18 y 19.

Crear un Store

Cualquier custom Hook puede convertirse en estado compartido al envolverlo con 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 devuelve el Hook que usan los componentes y su Provider correspondiente. Dentro del Store puedes seguir usando useState, useReducer, Context, Hooks de SDK o tus propios Hooks.

Mantén el initializer como un Hook con nombre en el nivel superior, por ejemplo useTaskStoreValue. Los initializers anónimos siguen funcionando, pero React Compiler no los compila automáticamente como Hooks en modo infer.

Montar el Provider

useTask solo puede llamarse dentro de los descendientes de TaskProvider.

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

Fuera del Provider, Kerros lanza un error claro.

Usar el Store en un componente

El selector devuelve únicamente los campos que utiliza el componente.

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

Kerros compara superficialmente los campos superiores del objeto selector. Mientras esos campos seleccionados sigan siendo iguales, otras actualizaciones del Store no vuelven a renderizar TaskList. El selector puede escribirse en línea y no necesita useCallback.

Varias instancias

Cada Provider montado crea un Store independiente.

<TaskProvider>
  <h2>Tareas personales</h2>
  <TaskList />
</TaskProvider>

<TaskProvider>
  <h2>Tareas del equipo</h2>
  <TaskList />
</TaskProvider>

Cada TaskList lee automáticamente el Provider más cercano. Los datos permanecen aislados.

Dependencias entre Stores

Un Store puede llamar directamente a otro Store.

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)

Monta el Provider de la dependencia por fuera y mantén la dependencia en una sola dirección.

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

Pasar props al Provider

Las props del Provider se pasan al Hook del Store.

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

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

Probarlo

Este contador utiliza la misma API createStore + Provider + selector.

Continúa con Selectores y Composición de Stores.