Skip to content

Commit 412bb4d

Browse files
committed
use useLayoutEffect instead of useEffect in useReactiveVar (fixes #8012)
1 parent 8e1cf16 commit 412bb4d

2 files changed

Lines changed: 6 additions & 19 deletions

File tree

src/cache/inmemory/reactiveVars.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,6 @@ export type ReactiveListener<T> = (value: T) => any;
1616
// called in Policies#readField.
1717
export const cacheSlot = new Slot<ApolloCache<any>>();
1818

19-
// A listener function could in theory cause another listener to be added
20-
// to the set while we're iterating over it, so it's important to commit
21-
// to the original elements of the set before we begin iterating. See
22-
// iterateObserversSafely for another example of this pattern.
23-
function consumeAndIterate<T>(set: Set<T>, callback: (item: T) => any) {
24-
if (set.size) {
25-
const items: T[] = [];
26-
set.forEach(item => items.push(item));
27-
set.clear();
28-
items.forEach(callback);
29-
}
30-
}
31-
3219
const cacheInfoMap = new WeakMap<ApolloCache<any>, {
3320
vars: Set<ReactiveVar<any>>;
3421
dep: OptimisticDependencyFunction<ReactiveVar<any>>;
@@ -79,7 +66,9 @@ export function makeVar<T>(value: T): ReactiveVar<T> {
7966
broadcast(cache);
8067
});
8168
// Finally, notify any listeners added via rv.onNextChange.
82-
consumeAndIterate(listeners, listener => listener(value));
69+
const oldListeners = Array.from(listeners);
70+
listeners.clear();
71+
oldListeners.forEach(listener => listener(value));
8372
}
8473
} else {
8574
// When reading from the variable, obtain the current cache from

src/react/hooks/useReactiveVar.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect, useLayoutEffect } from 'react';
22
import { ReactiveVar } from '../../core';
33

44
export function useReactiveVar<T>(rv: ReactiveVar<T>): T {
@@ -9,7 +9,7 @@ export function useReactiveVar<T>(rv: ReactiveVar<T>): T {
99
// We subscribe to variable updates on initial mount and when the value has
1010
// changed. This avoids a subtle bug in React.StrictMode where multiple listeners
1111
// are added, leading to inconsistent updates.
12-
useEffect(() => rv.onNextChange(setValue), [value]);
12+
useLayoutEffect(() => rv.onNextChange(setValue), [value]);
1313
// Once the component is unmounted, ignore future updates. Note that the
1414
// above useEffect function returns a mute function without calling it,
1515
// allowing it to be called when the component unmounts. This is
@@ -24,9 +24,7 @@ export function useReactiveVar<T>(rv: ReactiveVar<T>): T {
2424
// a useEffect higher in the component tree changing a variable's value
2525
// before the above useEffect can set the onNextChange handler. Note that React
2626
// will not schedule an update if setState is called with the same value as before.
27-
useEffect(() => {
28-
setValue(rv())
29-
}, []);
27+
useEffect(() => setValue(rv()), []);
3028

3129
return value;
3230
}

0 commit comments

Comments
 (0)