addons/isl/src/third-party/jotai-devtools/utils/useAtomsSnapshot.tsblame
View source
b69ab311import { useStore } from 'jotai/react';
b69ab312import { useEffect, useLayoutEffect, useRef, useState } from 'react';
b69ab313import type {
b69ab314 AtomsDependents,
b69ab315 AtomsSnapshot,
b69ab316 AtomsValues,
b69ab317 Options,
b69ab318 Store,
b69ab319} from '../types';
b69ab3110
b69ab3111const isEqualAtomsValues = (left: AtomsValues, right: AtomsValues) =>
b69ab3112 left.size === right.size &&
b69ab3113 Array.from(left).every(([left, v]) => Object.is(right.get(left), v));
b69ab3114
b69ab3115const isEqualAtomsDependents = (
b69ab3116 left: AtomsDependents,
b69ab3117 right: AtomsDependents,
b69ab3118) =>
b69ab3119 left.size === right.size &&
b69ab3120 Array.from(left).every(([a, dLeft]) => {
b69ab3121 const dRight = right.get(a);
b69ab3122 return (
b69ab3123 dRight &&
b69ab3124 dLeft.size === dRight.size &&
b69ab3125 Array.from(dLeft).every((d) => dRight.has(d))
b69ab3126 );
b69ab3127 });
b69ab3128
b69ab3129export type SnapshotOptions = Options & {
b69ab3130 /**
b69ab3131 * Defaults to `false`
b69ab3132 *
b69ab3133 * Private are atoms that are used by Jotai libraries internally to manage state.
b69ab3134 * They're often used internally in atoms like `atomWithStorage` or `atomWithLocation`, etc. to manage state.
b69ab3135 */
b69ab3136 shouldShowPrivateAtoms?: boolean;
b69ab3137};
b69ab3138
b69ab3139export function useAtomsSnapshot({
b69ab3140 shouldShowPrivateAtoms = false,
b69ab3141 ...options
b69ab3142}: SnapshotOptions = {}): AtomsSnapshot {
b69ab3143 const store = useStore(options);
b69ab3144
b69ab3145 const [atomsSnapshot, setAtomsSnapshot] = useState<AtomsSnapshot>(() => ({
b69ab3146 values: new Map(),
b69ab3147 dependents: new Map(),
b69ab3148 }));
b69ab3149
b69ab3150 const duringReactRenderPhase = useRef(true);
b69ab3151 duringReactRenderPhase.current = true;
b69ab3152 useLayoutEffect(() => {
b69ab3153 duringReactRenderPhase.current = false;
b69ab3154 });
b69ab3155
b69ab3156 useEffect(() => {
b69ab3157 const devSubscribeStore: Store['dev_subscribe_store'] =
b69ab3158 // @ts-expect-error dev_subscribe_state is deprecated in <= 2.0.3
b69ab3159 store?.dev_subscribe_store || store?.dev_subscribe_state;
b69ab3160
b69ab3161 if (!devSubscribeStore) return;
b69ab3162
b69ab3163 let prevValues: AtomsValues = new Map();
b69ab3164 let prevDependents: AtomsDependents = new Map();
b69ab3165
b69ab3166 if (!('dev_subscribe_store' in store)) {
b69ab3167 console.warn(
b69ab3168 '[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.',
b69ab3169 );
b69ab3170 }
b69ab3171
b69ab3172 // TODO remove this `t: any` and deprecation warnings in next breaking change release
b69ab3173 const callback = (
b69ab3174 type?: Parameters<Parameters<typeof devSubscribeStore>[0]>[0],
b69ab3175 ) => {
b69ab3176 if (typeof type !== 'object') {
b69ab3177 console.warn(
b69ab3178 '[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.',
b69ab3179 );
b69ab3180 }
b69ab3181
b69ab3182 const values: AtomsValues = new Map();
b69ab3183 const dependents: AtomsDependents = new Map();
b69ab3184 for (const atom of store.dev_get_mounted_atoms?.() || []) {
b69ab3185 if (!shouldShowPrivateAtoms && atom.debugPrivate) {
b69ab3186 // Skip private atoms
b69ab3187 continue;
b69ab3188 }
b69ab3189
b69ab3190 const atomState = store.dev_get_atom_state?.(atom);
b69ab3191 if (atomState) {
b69ab3192 if ('v' in atomState) {
b69ab3193 values.set(atom, atomState.v);
b69ab3194 }
b69ab3195 }
b69ab3196 const mounted = store.dev_get_mounted?.(atom);
b69ab3197 if (mounted) {
b69ab3198 let atomDependents = mounted.t;
b69ab3199
b69ab31100 if (!shouldShowPrivateAtoms) {
b69ab31101 // Filter private dependent atoms
b69ab31102 atomDependents = new Set(
b69ab31103 Array.from(atomDependents.values()).filter(
b69ab31104 /* NOTE: This just removes private atoms from the dependents list,
b69ab31105 instead of hiding them from the dependency chain and showing
b69ab31106 the nested dependents of the private atoms. */
b69ab31107 (dependent) => !dependent.debugPrivate,
b69ab31108 ),
b69ab31109 );
b69ab31110 }
b69ab31111
b69ab31112 dependents.set(atom, atomDependents);
b69ab31113 }
b69ab31114 }
b69ab31115 if (
b69ab31116 isEqualAtomsValues(prevValues, values) &&
b69ab31117 isEqualAtomsDependents(prevDependents, dependents)
b69ab31118 ) {
b69ab31119 // not changed
b69ab31120 return;
b69ab31121 }
b69ab31122 prevValues = values;
b69ab31123 prevDependents = dependents;
b69ab31124 const deferrableAtomSetAction = () =>
b69ab31125 setAtomsSnapshot({ values, dependents });
b69ab31126 if (duringReactRenderPhase.current) {
b69ab31127 // avoid set action when react is rendering components
b69ab31128 Promise.resolve().then(deferrableAtomSetAction);
b69ab31129 } else {
b69ab31130 deferrableAtomSetAction();
b69ab31131 }
b69ab31132 };
b69ab31133 const unsubscribe = devSubscribeStore?.(callback, 2);
b69ab31134 callback({} as any);
b69ab31135 return unsubscribe;
b69ab31136 }, [store, shouldShowPrivateAtoms]);
b69ab31137
b69ab31138 return atomsSnapshot;
b69ab31139}