Skip to content

Commit

Permalink
fix: defer setAtom in subscribers of store change during main render …
Browse files Browse the repository at this point in the history
…to next micro task (#109)

* fix: make subscribers of store changes async

* fix: wrap only the setAtom action with promise

* fix: remove defer in useInternalAtomValue

* fix: remove defer in useInternalAtomValue

* fix: fix typo

* refactor: avoid ternary operators and rename variables
  • Loading branch information
yf-yang authored Dec 4, 2023
1 parent 12ba74a commit 2cdc81d
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 6 deletions.
22 changes: 20 additions & 2 deletions src/utils/useAtomsDebugValue.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { useDebugValue, useEffect, useState } from 'react';
import {
useDebugValue,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import { useStore } from 'jotai/react';
import type { Atom } from 'jotai/vanilla';

Expand Down Expand Up @@ -42,6 +48,11 @@ export const useAtomsDebugValue = (options?: Options) => {
const enabled = options?.enabled ?? __DEV__;
const store = useStore(options);
const [atoms, setAtoms] = useState<Atom<unknown>[]>([]);
const duringReactRenderPhase = useRef(true);
duringReactRenderPhase.current = true;
useLayoutEffect(() => {
duringReactRenderPhase.current = false;
});
useEffect(() => {
const devSubscribeStore: Store['dev_subscribe_store'] =
// @ts-expect-error dev_subscribe_state is deprecated in <= 2.0.3
Expand All @@ -51,7 +62,14 @@ export const useAtomsDebugValue = (options?: Options) => {
return;
}
const callback = () => {
setAtoms(Array.from(store.dev_get_mounted_atoms?.() || []));
const deferrableAtomSetAction = () =>
setAtoms(Array.from(store.dev_get_mounted_atoms?.() || []));
if (duringReactRenderPhase.current) {
// avoid set action when react is rendering components
Promise.resolve().then(deferrableAtomSetAction);
} else {
deferrableAtomSetAction();
}
};
// FIXME replace this with `store.dev_subscribe_store` check after next minor Jotai 2.1.0?
if (!('dev_subscribe_store' in store)) {
Expand Down
21 changes: 17 additions & 4 deletions src/utils/useAtomsSnapshot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useStore } from 'jotai/react';
import type {
AtomsDependents,
Expand Down Expand Up @@ -47,6 +47,12 @@ export function useAtomsSnapshot({
dependents: new Map(),
}));

const duringReactRenderPhase = useRef(true);
duringReactRenderPhase.current = true;
useLayoutEffect(() => {
duringReactRenderPhase.current = false;
});

useEffect(() => {
const devSubscribeStore: Store['dev_subscribe_store'] =
// @ts-expect-error dev_subscribe_state is deprecated in <= 2.0.3
Expand Down Expand Up @@ -96,8 +102,8 @@ export function useAtomsSnapshot({
atomDependents = new Set(
Array.from(atomDependents.values()).filter(
/* NOTE: This just removes private atoms from the dependents list,
instead of hiding them from the dependency chain and showing
the nested dependents of the private atoms. */
instead of hiding them from the dependency chain and showing
the nested dependents of the private atoms. */
(dependent) => !dependent.debugPrivate,
),
);
Expand All @@ -115,7 +121,14 @@ export function useAtomsSnapshot({
}
prevValues = values;
prevDependents = dependents;
setAtomsSnapshot({ values, dependents });
const deferrableAtomSetAction = () =>
setAtomsSnapshot({ values, dependents });
if (duringReactRenderPhase.current) {
// avoid set action when react is rendering components
Promise.resolve().then(deferrableAtomSetAction);
} else {
deferrableAtomSetAction();
}
};
const unsubscribe = devSubscribeStore?.(callback, 2);
callback({} as any);
Expand Down

0 comments on commit 2cdc81d

Please sign in to comment.