All files / src/utils cellStore.ts

0% Statements 0/11
0% Branches 0/2
0% Functions 0/6
0% Lines 0/9

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97                                                                                                                                                                                                 
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<T> = {
  /**
   * 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<T>(initial: T): Cell<T> {
  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 (
 *     <button onClick={() => count.set(v => v + 1)}>
 *       Count: {value}
 *     </button>
 *   );
 * }
 * ```
 */
export function useCell<T>(c: Cell<T>) {
  return useSyncExternalStore(c.subscribe, c.get, c.get);
}