UNPKG

nucleux

Version:

Simple, atomic hub for all your React application's state management needs. No providers, no boilerplate, just state that works.

175 lines (174 loc) 6.44 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useValue = exports.useStore = exports.useNucleux = void 0; const react_1 = require("react"); const shim_1 = require("use-sync-external-store/shim"); const Atom_1 = require("./Atom"); const Container_1 = require("./Container"); const utils_1 = require("./utils"); /** * Provides access to a Nucleux store instance. * * This hook retrieves a store instance from the container and ensures proper * cleanup when the component unmounts. * * @template S - The store type that extends the base Store class * @param {StoreConstructable<S>} store - The store class constructor * @returns {S} The store instance * * @example * // Define your store * class CounterStore extends Store { * count = this.atom(0); * * increment() { * this.count.value++; * } * } * * // Use the store in a component * function Counter() { * const counterStore = useStore(CounterStore); * const count = useValue(counterStore.count); * * return ( * <div> * <p>Count: {count}</p> * <button onClick={counterStore.increment}>Increment</button> * </div> * ); * } */ function useStore(store) { const container = Container_1.Container.getInstance(); const [getStore, cleanup] = (0, react_1.useMemo)(() => { const storeInstance = container.get(store); return [ () => storeInstance, () => { return () => { container.remove(store); }; }, ]; }, [store]); return (0, shim_1.useSyncExternalStore)(cleanup, getStore, getStore); } exports.useStore = useStore; function useValue(atomOrStore, atomKey) { if ((0, utils_1.isAtom)(atomOrStore) && atomOrStore instanceof Atom_1.Atom) { const [getter, subscribe, getServerSnapshot] = (0, react_1.useMemo)(() => { return [ () => atomOrStore.value, (onStoreChange) => { const subId = atomOrStore.subscribe(onStoreChange); return () => { atomOrStore.unsubscribe(subId); }; }, () => atomOrStore.initialValue, ]; }, [atomOrStore, atomKey]); return (0, shim_1.useSyncExternalStore)(subscribe, getter, getServerSnapshot); } if (atomKey !== undefined) { const [getter, subscribe, getServerSnapshot] = (0, react_1.useMemo)(() => { const container = Container_1.Container.getInstance(); const storeInstance = container.get(atomOrStore); const atom = storeInstance[atomKey]; if (!(0, utils_1.isAtom)(atom) || !(atom instanceof Atom_1.Atom)) { throw new Error(`Property "${String(atomKey)}" is not an atom`); } const typedAtom = atom; return [ () => typedAtom.value, (onStoreChange) => { const subId = typedAtom.subscribe(onStoreChange); return () => { typedAtom.unsubscribe(subId); // We're only using this atom, so can release the store reference container.remove(atomOrStore); }; }, () => typedAtom.initialValue, ]; }, [atomOrStore, atomKey]); return (0, shim_1.useSyncExternalStore)(subscribe, getter, getServerSnapshot); } throw new Error('Invalid arguments to useValue'); } exports.useValue = useValue; /** * Provides access to all methods and state values from a Nucleux store in a single hook. * * This hook creates a reactive proxy to the store that automatically updates when any atom * in the store changes. It's designed for components that need access to multiple atoms * from the same store without having to use multiple hooks. * * @template S - The store type that extends the base Store class * @param {StoreConstructable<S>} store - The store class constructor * @returns {StoreProxy<S>} A proxy object containing both store methods and atom values * * @example * // Define your store * class CounterStore extends Store { * count = this.atom(0); * * increment() { * this.count.value++; * } * * decrement() { * this.count.value--; * } * } * * // Use the store in a component * function Counter() { * const counter = useNucleux(CounterStore); * * // Access both methods and state directly * return ( * <div> * <p>Count: {counter.count}</p> * <button onClick={counter.increment}>+</button> * <button onClick={counter.decrement}>-</button> * </div> * ); * } * * @remarks * - This hook subscribes to all atoms in the store, so components will re-render on any state change. * - For more selective reactivity, consider using `useStore` with individual `useValue` hooks instead. * - Cannot directly modify atom values through the proxy; use store methods for state updates. */ function useNucleux(store) { const container = Container_1.Container.getInstance(); const [getSnapshot, subscribe, getServerSnapshot] = (0, react_1.useMemo)(() => { const storeInstance = container.get(store); let proxy = (0, utils_1.getStoreProxy)(storeInstance); const getSnapshotFn = () => proxy; const getServerSnapshotFn = () => (0, utils_1.getStoreProxy)(storeInstance, true); return [ getSnapshotFn, (onStoreChange) => { for (const key in storeInstance) { const potentialAtom = storeInstance[key]; if ((0, utils_1.isAtom)(potentialAtom) && potentialAtom instanceof Atom_1.Atom) { // @ts-expect-error protected store method storeInstance.watchAtom(potentialAtom, () => { proxy = (0, utils_1.getStoreProxy)(storeInstance); onStoreChange(); }, true); } } return () => { container.remove(store); }; }, getServerSnapshotFn, ]; }, [store]); return (0, shim_1.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot); } exports.useNucleux = useNucleux;