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
JavaScript
;
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;