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