import {useSyncExternalStore} from "react"; type Unsub = () => void; /** * A simple reactive state container that holds a value of type `T` that provides methods to get, set, and subscribe. */ export type Cell = { /** * Returns the current value stored in the cell. */ get: () => T; /** * Updates the cell's value, pass either a direct value or an updater function. * * @example * ```ts * count.set(5); * count.set(prev => prev + 1); * ``` */ set: (next: T | ((prev: T) => T)) => void; /** * Subscribe to changes in the cell's value, meaning the provided callback is called whenever the value changes. * Returns an unsubscribe function. * * @example * ```ts * const unsubscribe = count.subscribe(() => console.log(count.get())); * // later: * unsubscribe(); * ``` */ subscribe: (callback: () => void) => Unsub; }; /** * Creates a new reactive state container (`Cell`) with an initial value. * * This function allows you to store and mutate state outside of React, * while still supporting subscriptions for reactivity. * * @param initial - The initial value for the cell. * @returns A Cell object with `get`, `set`, and `subscribe` methods. * * @example * ```ts * const count = cell(0); * count.set(10); * console.log(count.get()); // 10 * ``` */ export function cell(initial: T): Cell { let value = initial; const listeners = new Set<() => void>(); return { get: () => value, set: (next) => { value = typeof next === "function" ? (next as (v: T) => T)(value) : next; for (const l of listeners) l(); }, subscribe: (callback) => { listeners.add(callback); return () => listeners.delete(callback); }, }; } /** * React hook that subscribes a component to a Cell. * * Automatically re-renders the component whenever the Cell's value changes. * Uses React’s built-in `useSyncExternalStore` for correct subscription behavior. * * @param c - The cell to subscribe to. * @returns The current value of the cell. * * @example * ```tsx * const count = cell(0); * * function Counter() { * const value = useCell(count); * return ( * * ); * } * ``` */ export function useCell(c: Cell) { return useSyncExternalStore(c.subscribe, c.get, c.get); }