4.4 KB140 lines
Blame
1import { useStore } from 'jotai/react';
2import { useEffect, useLayoutEffect, useRef, useState } from 'react';
3import type {
4 AtomsDependents,
5 AtomsSnapshot,
6 AtomsValues,
7 Options,
8 Store,
9} from '../types';
10
11const isEqualAtomsValues = (left: AtomsValues, right: AtomsValues) =>
12 left.size === right.size &&
13 Array.from(left).every(([left, v]) => Object.is(right.get(left), v));
14
15const isEqualAtomsDependents = (
16 left: AtomsDependents,
17 right: AtomsDependents,
18) =>
19 left.size === right.size &&
20 Array.from(left).every(([a, dLeft]) => {
21 const dRight = right.get(a);
22 return (
23 dRight &&
24 dLeft.size === dRight.size &&
25 Array.from(dLeft).every((d) => dRight.has(d))
26 );
27 });
28
29export type SnapshotOptions = Options & {
30 /**
31 * Defaults to `false`
32 *
33 * Private are atoms that are used by Jotai libraries internally to manage state.
34 * They're often used internally in atoms like `atomWithStorage` or `atomWithLocation`, etc. to manage state.
35 */
36 shouldShowPrivateAtoms?: boolean;
37};
38
39export function useAtomsSnapshot({
40 shouldShowPrivateAtoms = false,
41 ...options
42}: SnapshotOptions = {}): AtomsSnapshot {
43 const store = useStore(options);
44
45 const [atomsSnapshot, setAtomsSnapshot] = useState<AtomsSnapshot>(() => ({
46 values: new Map(),
47 dependents: new Map(),
48 }));
49
50 const duringReactRenderPhase = useRef(true);
51 duringReactRenderPhase.current = true;
52 useLayoutEffect(() => {
53 duringReactRenderPhase.current = false;
54 });
55
56 useEffect(() => {
57 const devSubscribeStore: Store['dev_subscribe_store'] =
58 // @ts-expect-error dev_subscribe_state is deprecated in <= 2.0.3
59 store?.dev_subscribe_store || store?.dev_subscribe_state;
60
61 if (!devSubscribeStore) return;
62
63 let prevValues: AtomsValues = new Map();
64 let prevDependents: AtomsDependents = new Map();
65
66 if (!('dev_subscribe_store' in store)) {
67 console.warn(
68 '[DEPRECATION-WARNING]: Your Jotai version is out-of-date and contains deprecated properties that will be removed soon. Please update to the latest version of Jotai.',
69 );
70 }
71
72 // TODO remove this `t: any` and deprecation warnings in next breaking change release
73 const callback = (
74 type?: Parameters<Parameters<typeof devSubscribeStore>[0]>[0],
75 ) => {
76 if (typeof type !== 'object') {
77 console.warn(
78 '[DEPRECATION-WARNING]: Your Jotai version is out-of-date and contains deprecated properties that will be removed soon. Please update to the latest version of Jotai.',
79 );
80 }
81
82 const values: AtomsValues = new Map();
83 const dependents: AtomsDependents = new Map();
84 for (const atom of store.dev_get_mounted_atoms?.() || []) {
85 if (!shouldShowPrivateAtoms && atom.debugPrivate) {
86 // Skip private atoms
87 continue;
88 }
89
90 const atomState = store.dev_get_atom_state?.(atom);
91 if (atomState) {
92 if ('v' in atomState) {
93 values.set(atom, atomState.v);
94 }
95 }
96 const mounted = store.dev_get_mounted?.(atom);
97 if (mounted) {
98 let atomDependents = mounted.t;
99
100 if (!shouldShowPrivateAtoms) {
101 // Filter private dependent atoms
102 atomDependents = new Set(
103 Array.from(atomDependents.values()).filter(
104 /* NOTE: This just removes private atoms from the dependents list,
105 instead of hiding them from the dependency chain and showing
106 the nested dependents of the private atoms. */
107 (dependent) => !dependent.debugPrivate,
108 ),
109 );
110 }
111
112 dependents.set(atom, atomDependents);
113 }
114 }
115 if (
116 isEqualAtomsValues(prevValues, values) &&
117 isEqualAtomsDependents(prevDependents, dependents)
118 ) {
119 // not changed
120 return;
121 }
122 prevValues = values;
123 prevDependents = dependents;
124 const deferrableAtomSetAction = () =>
125 setAtomsSnapshot({ values, dependents });
126 if (duringReactRenderPhase.current) {
127 // avoid set action when react is rendering components
128 Promise.resolve().then(deferrableAtomSetAction);
129 } else {
130 deferrableAtomSetAction();
131 }
132 };
133 const unsubscribe = devSubscribeStore?.(callback, 2);
134 callback({} as any);
135 return unsubscribe;
136 }, [store, shouldShowPrivateAtoms]);
137
138 return atomsSnapshot;
139}
140