2.8 KB89 lines
Blame
1import { isDev as __DEV__ } from '../../../utils';
2
3import { useStore } from 'jotai/react';
4import type { Atom } from 'jotai/vanilla';
5import {
6 useDebugValue,
7 useEffect,
8 useLayoutEffect,
9 useRef,
10 useState,
11} from 'react';
12
13type Store = ReturnType<typeof useStore>;
14type AtomState = NonNullable<
15 ReturnType<NonNullable<Store['dev_get_atom_state']>>
16>;
17
18const atomToPrintable = (atom: Atom<unknown>) =>
19 atom.debugLabel || atom.toString();
20
21const stateToPrintable = ([store, atoms]: [Store, Atom<unknown>[]]) =>
22 Object.fromEntries(
23 atoms.flatMap((atom) => {
24 const mounted = store.dev_get_mounted?.(atom);
25 if (!mounted) {
26 return [];
27 }
28 const dependents = mounted.t;
29 const atomState = store.dev_get_atom_state?.(atom) || ({} as AtomState);
30 return [
31 [
32 atomToPrintable(atom),
33 {
34 ...('e' in atomState && { error: atomState.e }),
35 ...('v' in atomState && { value: atomState.v }),
36 dependents: Array.from(dependents).map(atomToPrintable),
37 },
38 ],
39 ];
40 }),
41 );
42
43type Options = Parameters<typeof useStore>[0] & {
44 enabled?: boolean;
45};
46
47// We keep a reference to the atoms,
48// so atoms aren't garbage collected by the WeakMap of mounted atoms
49export const useAtomsDebugValue = (options?: Options) => {
50 const enabled = options?.enabled ?? __DEV__;
51 const store = useStore(options);
52 const [atoms, setAtoms] = useState<Atom<unknown>[]>([]);
53 const duringReactRenderPhase = useRef(true);
54 duringReactRenderPhase.current = true;
55 useLayoutEffect(() => {
56 duringReactRenderPhase.current = false;
57 });
58 useEffect(() => {
59 const devSubscribeStore: Store['dev_subscribe_store'] =
60 // @ts-expect-error dev_subscribe_state is deprecated in <= 2.0.3
61 store?.dev_subscribe_store || store?.dev_subscribe_state;
62
63 if (!enabled || !devSubscribeStore) {
64 return;
65 }
66 const callback = () => {
67 const deferrableAtomSetAction = () =>
68 setAtoms(Array.from(store.dev_get_mounted_atoms?.() || []));
69 if (duringReactRenderPhase.current) {
70 // avoid set action when react is rendering components
71 Promise.resolve().then(deferrableAtomSetAction);
72 } else {
73 deferrableAtomSetAction();
74 }
75 };
76 // FIXME replace this with `store.dev_subscribe_store` check after next minor Jotai 2.1.0?
77 if (!('dev_subscribe_store' in store)) {
78 console.warn(
79 "[DEPRECATION-WARNING] Jotai version you're using contains deprecated dev-only properties that will be removed soon. Please update to the latest version of Jotai.",
80 );
81 }
82
83 const unsubscribe = devSubscribeStore?.(callback, 2);
84 callback();
85 return unsubscribe;
86 }, [enabled, store]);
87 useDebugValue([store, atoms], stateToPrintable);
88};
89