Skip to content

Commit 40eaa22

Browse files
authored
Remove dependency on Offscreen Fiber updateQueue for React Cache (#23229)
We need to use the Offscreen Fiber's update queue for interaction tracing. This PR removes the optimization that React Cache uses to not need to push and pop the cache in special circumstances and defaults to always pushing and popping the cache as long as there was a previous cache.
1 parent caf6d47 commit 40eaa22

10 files changed

+150
-146
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.new.js

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ import {
234234
pushRootCachePool,
235235
CacheContext,
236236
getSuspendedCachePool,
237-
restoreSpawnedCachePool,
237+
pushSpawnedCachePool,
238238
getOffscreenDeferredCachePool,
239239
} from './ReactFiberCacheComponent.new';
240240
import {createCapturedValue} from './ReactCapturedValue';
@@ -635,11 +635,6 @@ function updateOffscreenComponent(
635635
const prevState: OffscreenState | null =
636636
current !== null ? current.memoizedState : null;
637637

638-
// If this is not null, this is a cache pool that was carried over from the
639-
// previous render. We will push this to the cache pool context so that we can
640-
// resume in-flight requests.
641-
let spawnedCachePool: SpawnedCachePool | null = null;
642-
643638
if (
644639
nextProps.mode === 'hidden' ||
645640
nextProps.mode === 'unstable-defer-without-hiding'
@@ -652,8 +647,16 @@ function updateOffscreenComponent(
652647
cachePool: null,
653648
};
654649
workInProgress.memoizedState = nextState;
650+
if (enableCache) {
651+
// push the cache pool even though we're going to bail out
652+
// because otherwise there'd be a context mismatch
653+
if (current !== null) {
654+
pushSpawnedCachePool(workInProgress, null);
655+
}
656+
}
655657
pushRenderLanes(workInProgress, renderLanes);
656658
} else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
659+
let spawnedCachePool: SpawnedCachePool | null = null;
657660
// We're hidden, and we're not rendering at Offscreen. We will bail out
658661
// and resume this tree later.
659662
let nextBaseLanes;
@@ -663,9 +666,6 @@ function updateOffscreenComponent(
663666
if (enableCache) {
664667
// Save the cache pool so we can resume later.
665668
spawnedCachePool = getOffscreenDeferredCachePool();
666-
// We don't need to push to the cache pool because we're about to
667-
// bail out. There won't be a context mismatch because we only pop
668-
// the cache pool if `updateQueue` is non-null.
669669
}
670670
} else {
671671
nextBaseLanes = renderLanes;
@@ -681,6 +681,14 @@ function updateOffscreenComponent(
681681
};
682682
workInProgress.memoizedState = nextState;
683683
workInProgress.updateQueue = null;
684+
if (enableCache) {
685+
// push the cache pool even though we're going to bail out
686+
// because otherwise there'd be a context mismatch
687+
if (current !== null) {
688+
pushSpawnedCachePool(workInProgress, null);
689+
}
690+
}
691+
684692
// We're about to bail out, but we need to push this to the stack anyway
685693
// to avoid a push/pop misalignment.
686694
pushRenderLanes(workInProgress, nextBaseLanes);
@@ -701,19 +709,6 @@ function updateOffscreenComponent(
701709
// This is the second render. The surrounding visible content has already
702710
// committed. Now we resume rendering the hidden tree.
703711

704-
if (enableCache && prevState !== null) {
705-
// If the render that spawned this one accessed the cache pool, resume
706-
// using the same cache. Unless the parent changed, since that means
707-
// there was a refresh.
708-
const prevCachePool = prevState.cachePool;
709-
if (prevCachePool !== null) {
710-
spawnedCachePool = restoreSpawnedCachePool(
711-
workInProgress,
712-
prevCachePool,
713-
);
714-
}
715-
}
716-
717712
// Rendering at offscreen, so we can clear the base lanes.
718713
const nextState: OffscreenState = {
719714
baseLanes: NoLanes,
@@ -723,6 +718,14 @@ function updateOffscreenComponent(
723718
// Push the lanes that were skipped when we bailed out.
724719
const subtreeRenderLanes =
725720
prevState !== null ? prevState.baseLanes : renderLanes;
721+
if (enableCache && current !== null) {
722+
// If the render that spawned this one accessed the cache pool, resume
723+
// using the same cache. Unless the parent changed, since that means
724+
// there was a refresh.
725+
const prevCachePool = prevState !== null ? prevState.cachePool : null;
726+
pushSpawnedCachePool(workInProgress, prevCachePool);
727+
}
728+
726729
pushRenderLanes(workInProgress, subtreeRenderLanes);
727730
}
728731
} else {
@@ -738,12 +741,7 @@ function updateOffscreenComponent(
738741
// using the same cache. Unless the parent changed, since that means
739742
// there was a refresh.
740743
const prevCachePool = prevState.cachePool;
741-
if (prevCachePool !== null) {
742-
spawnedCachePool = restoreSpawnedCachePool(
743-
workInProgress,
744-
prevCachePool,
745-
);
746-
}
744+
pushSpawnedCachePool(workInProgress, prevCachePool);
747745
}
748746

749747
// Since we're not hidden anymore, reset the state
@@ -753,16 +751,19 @@ function updateOffscreenComponent(
753751
// special to do. Need to push to the stack regardless, though, to avoid
754752
// a push/pop misalignment.
755753
subtreeRenderLanes = renderLanes;
754+
755+
if (enableCache) {
756+
// If the render that spawned this one accessed the cache pool, resume
757+
// using the same cache. Unless the parent changed, since that means
758+
// there was a refresh.
759+
if (current !== null) {
760+
pushSpawnedCachePool(workInProgress, null);
761+
}
762+
}
756763
}
757764
pushRenderLanes(workInProgress, subtreeRenderLanes);
758765
}
759766

760-
if (enableCache) {
761-
// If we have a cache pool from a previous render attempt, then this will be
762-
// non-null. We use this to infer whether to push/pop the cache context.
763-
workInProgress.updateQueue = spawnedCachePool;
764-
}
765-
766767
if (enablePersistentOffscreenHostContainer && supportsPersistence) {
767768
// In persistent mode, the offscreen children are wrapped in a host node.
768769
// TODO: Optimize this to use the OffscreenComponent fiber instead of
@@ -2072,6 +2073,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
20722073

20732074
const nextPrimaryChildren = nextProps.children;
20742075
const nextFallbackChildren = nextProps.fallback;
2076+
20752077
if (showFallback) {
20762078
const fallbackFragment = mountSuspenseFallbackChildren(
20772079
workInProgress,

packages/react-reconciler/src/ReactFiberBeginWork.old.js

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ import {
234234
pushRootCachePool,
235235
CacheContext,
236236
getSuspendedCachePool,
237-
restoreSpawnedCachePool,
237+
pushSpawnedCachePool,
238238
getOffscreenDeferredCachePool,
239239
} from './ReactFiberCacheComponent.old';
240240
import {createCapturedValue} from './ReactCapturedValue';
@@ -635,11 +635,6 @@ function updateOffscreenComponent(
635635
const prevState: OffscreenState | null =
636636
current !== null ? current.memoizedState : null;
637637

638-
// If this is not null, this is a cache pool that was carried over from the
639-
// previous render. We will push this to the cache pool context so that we can
640-
// resume in-flight requests.
641-
let spawnedCachePool: SpawnedCachePool | null = null;
642-
643638
if (
644639
nextProps.mode === 'hidden' ||
645640
nextProps.mode === 'unstable-defer-without-hiding'
@@ -652,8 +647,16 @@ function updateOffscreenComponent(
652647
cachePool: null,
653648
};
654649
workInProgress.memoizedState = nextState;
650+
if (enableCache) {
651+
// push the cache pool even though we're going to bail out
652+
// because otherwise there'd be a context mismatch
653+
if (current !== null) {
654+
pushSpawnedCachePool(workInProgress, null);
655+
}
656+
}
655657
pushRenderLanes(workInProgress, renderLanes);
656658
} else if (!includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
659+
let spawnedCachePool: SpawnedCachePool | null = null;
657660
// We're hidden, and we're not rendering at Offscreen. We will bail out
658661
// and resume this tree later.
659662
let nextBaseLanes;
@@ -663,9 +666,6 @@ function updateOffscreenComponent(
663666
if (enableCache) {
664667
// Save the cache pool so we can resume later.
665668
spawnedCachePool = getOffscreenDeferredCachePool();
666-
// We don't need to push to the cache pool because we're about to
667-
// bail out. There won't be a context mismatch because we only pop
668-
// the cache pool if `updateQueue` is non-null.
669669
}
670670
} else {
671671
nextBaseLanes = renderLanes;
@@ -681,6 +681,14 @@ function updateOffscreenComponent(
681681
};
682682
workInProgress.memoizedState = nextState;
683683
workInProgress.updateQueue = null;
684+
if (enableCache) {
685+
// push the cache pool even though we're going to bail out
686+
// because otherwise there'd be a context mismatch
687+
if (current !== null) {
688+
pushSpawnedCachePool(workInProgress, null);
689+
}
690+
}
691+
684692
// We're about to bail out, but we need to push this to the stack anyway
685693
// to avoid a push/pop misalignment.
686694
pushRenderLanes(workInProgress, nextBaseLanes);
@@ -701,19 +709,6 @@ function updateOffscreenComponent(
701709
// This is the second render. The surrounding visible content has already
702710
// committed. Now we resume rendering the hidden tree.
703711

704-
if (enableCache && prevState !== null) {
705-
// If the render that spawned this one accessed the cache pool, resume
706-
// using the same cache. Unless the parent changed, since that means
707-
// there was a refresh.
708-
const prevCachePool = prevState.cachePool;
709-
if (prevCachePool !== null) {
710-
spawnedCachePool = restoreSpawnedCachePool(
711-
workInProgress,
712-
prevCachePool,
713-
);
714-
}
715-
}
716-
717712
// Rendering at offscreen, so we can clear the base lanes.
718713
const nextState: OffscreenState = {
719714
baseLanes: NoLanes,
@@ -723,6 +718,14 @@ function updateOffscreenComponent(
723718
// Push the lanes that were skipped when we bailed out.
724719
const subtreeRenderLanes =
725720
prevState !== null ? prevState.baseLanes : renderLanes;
721+
if (enableCache && current !== null) {
722+
// If the render that spawned this one accessed the cache pool, resume
723+
// using the same cache. Unless the parent changed, since that means
724+
// there was a refresh.
725+
const prevCachePool = prevState !== null ? prevState.cachePool : null;
726+
pushSpawnedCachePool(workInProgress, prevCachePool);
727+
}
728+
726729
pushRenderLanes(workInProgress, subtreeRenderLanes);
727730
}
728731
} else {
@@ -738,12 +741,7 @@ function updateOffscreenComponent(
738741
// using the same cache. Unless the parent changed, since that means
739742
// there was a refresh.
740743
const prevCachePool = prevState.cachePool;
741-
if (prevCachePool !== null) {
742-
spawnedCachePool = restoreSpawnedCachePool(
743-
workInProgress,
744-
prevCachePool,
745-
);
746-
}
744+
pushSpawnedCachePool(workInProgress, prevCachePool);
747745
}
748746

749747
// Since we're not hidden anymore, reset the state
@@ -753,16 +751,19 @@ function updateOffscreenComponent(
753751
// special to do. Need to push to the stack regardless, though, to avoid
754752
// a push/pop misalignment.
755753
subtreeRenderLanes = renderLanes;
754+
755+
if (enableCache) {
756+
// If the render that spawned this one accessed the cache pool, resume
757+
// using the same cache. Unless the parent changed, since that means
758+
// there was a refresh.
759+
if (current !== null) {
760+
pushSpawnedCachePool(workInProgress, null);
761+
}
762+
}
756763
}
757764
pushRenderLanes(workInProgress, subtreeRenderLanes);
758765
}
759766

760-
if (enableCache) {
761-
// If we have a cache pool from a previous render attempt, then this will be
762-
// non-null. We use this to infer whether to push/pop the cache context.
763-
workInProgress.updateQueue = spawnedCachePool;
764-
}
765-
766767
if (enablePersistentOffscreenHostContainer && supportsPersistence) {
767768
// In persistent mode, the offscreen children are wrapped in a host node.
768769
// TODO: Optimize this to use the OffscreenComponent fiber instead of
@@ -2072,6 +2073,7 @@ function updateSuspenseComponent(current, workInProgress, renderLanes) {
20722073

20732074
const nextPrimaryChildren = nextProps.children;
20742075
const nextFallbackChildren = nextProps.fallback;
2076+
20752077
if (showFallback) {
20762078
const fallbackFragment = mountSuspenseFallbackChildren(
20772079
workInProgress,

packages/react-reconciler/src/ReactFiberCacheComponent.new.js

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -198,36 +198,26 @@ export function popRootCachePool(root: FiberRoot, renderLanes: Lanes) {
198198
// code organization purposes in case that changes.
199199
}
200200

201-
export function restoreSpawnedCachePool(
201+
export function pushSpawnedCachePool(
202202
offscreenWorkInProgress: Fiber,
203-
prevCachePool: SpawnedCachePool,
204-
): SpawnedCachePool | null {
203+
prevCachePool: SpawnedCachePool | null,
204+
): void {
205205
if (!enableCache) {
206-
return (null: any);
206+
return;
207207
}
208-
const nextParentCache = isPrimaryRenderer
209-
? CacheContext._currentValue
210-
: CacheContext._currentValue2;
211-
if (nextParentCache !== prevCachePool.parent) {
212-
// There was a refresh. Don't bother restoring anything since the refresh
213-
// will override it.
214-
return null;
208+
209+
if (prevCachePool === null) {
210+
push(resumedCache, resumedCache.current, offscreenWorkInProgress);
215211
} else {
216-
// No refresh. Resume with the previous cache. New Cache boundaries in the
217-
// subtree use this one instead of requesting a fresh one (see
218-
// peekCacheFromPool).
219212
push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
220-
221-
// Return the cache pool to signal that we did in fact push it. We will
222-
// assign this to the field on the fiber so we know to pop the context.
223-
return prevCachePool;
224213
}
225214
}
226215

227216
export function popCachePool(workInProgress: Fiber) {
228217
if (!enableCache) {
229218
return;
230219
}
220+
231221
pop(resumedCache, workInProgress);
232222
}
233223

packages/react-reconciler/src/ReactFiberCacheComponent.old.js

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -198,36 +198,26 @@ export function popRootCachePool(root: FiberRoot, renderLanes: Lanes) {
198198
// code organization purposes in case that changes.
199199
}
200200

201-
export function restoreSpawnedCachePool(
201+
export function pushSpawnedCachePool(
202202
offscreenWorkInProgress: Fiber,
203-
prevCachePool: SpawnedCachePool,
204-
): SpawnedCachePool | null {
203+
prevCachePool: SpawnedCachePool | null,
204+
): void {
205205
if (!enableCache) {
206-
return (null: any);
206+
return;
207207
}
208-
const nextParentCache = isPrimaryRenderer
209-
? CacheContext._currentValue
210-
: CacheContext._currentValue2;
211-
if (nextParentCache !== prevCachePool.parent) {
212-
// There was a refresh. Don't bother restoring anything since the refresh
213-
// will override it.
214-
return null;
208+
209+
if (prevCachePool === null) {
210+
push(resumedCache, resumedCache.current, offscreenWorkInProgress);
215211
} else {
216-
// No refresh. Resume with the previous cache. New Cache boundaries in the
217-
// subtree use this one instead of requesting a fresh one (see
218-
// peekCacheFromPool).
219212
push(resumedCache, prevCachePool.pool, offscreenWorkInProgress);
220-
221-
// Return the cache pool to signal that we did in fact push it. We will
222-
// assign this to the field on the fiber so we know to pop the context.
223-
return prevCachePool;
224213
}
225214
}
226215

227216
export function popCachePool(workInProgress: Fiber) {
228217
if (!enableCache) {
229218
return;
230219
}
220+
231221
pop(resumedCache, workInProgress);
232222
}
233223

0 commit comments

Comments
 (0)