alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
72 lines (61 loc) • 2.24 kB
text/typescript
import type { State, Static, TAtomObject } from "alepha";
import { Atom } from "alepha";
import { useCallback, useMemo, useSyncExternalStore } from "react";
import { useAlepha } from "./useAlepha.ts";
/**
* Hook to access and mutate the Alepha state.
*
* Reads through `useSyncExternalStore`, so the store — not a `useState` copy of
* it — is the single source of truth. That is what keeps the value correct when
* a mutation lands between render and effect-commit, when the component swaps
* which `target` it watches, and when React renders concurrently (no tearing).
*/
function useStore<T extends TAtomObject>(
target: Atom<T>,
defaultValue?: Static<T>,
): UseStoreReturn<Static<T>>;
function useStore<Key extends keyof State>(
target: Key,
defaultValue?: State[Key],
): UseStoreReturn<State[Key]>;
function useStore(target: any, defaultValue?: any): any {
const alepha = useAlepha();
const key: string = target instanceof Atom ? target.key : target;
// Seed during render (not in an effect) on purpose: effects don't run during
// SSR, and the default has to be in the store before `exportAtoms` serializes
// the hydration payload. Idempotent — only fills an empty slot.
useMemo(() => {
if (defaultValue != null && alepha.store.get(target) == null) {
alepha.store.set(target, defaultValue);
}
}, [defaultValue, key]);
const subscribe = useCallback(
(onStoreChange: () => void) => {
if (!alepha.isBrowser()) {
return () => {};
}
return alepha.events.on("state:mutate", (ev) => {
if (ev.key === key) {
onStoreChange();
}
});
},
[alepha, key],
);
// Read straight from the store rather than caching the event payload, so the
// snapshot stays correct no matter who mutated the key or when.
const getSnapshot = useCallback(
() => alepha.store.get(target),
[alepha, key],
);
const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
const setState = useCallback(
(value: any) => {
alepha.store.set(target, value);
},
[alepha, key],
);
return [state, setState] as const;
}
export type UseStoreReturn<T> = [T, (value: T) => void];
export { useStore };