Files
pepperplus-ui/src/utils/cellStore.ts
2025-11-26 13:41:18 +00:00

97 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 Reacts 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);
}