diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 5bb01e18078361..4f9b4ce42689c4 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -3181,13 +3181,12 @@ function commitPassiveMountOnFiber( // "Atomic" effects are ones that need to fire on every commit, // even during pre-rendering. An example is updating the reference // count on cache instances. - // TODO: Not yet implemented - // recursivelyTraverseAtomicPassiveEffects( - // finishedRoot, - // finishedWork, - // committedLanes, - // committedTransitions, - // ); + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); } } else { // Legacy Mode: Fire the effects even if the tree is hidden. @@ -3363,13 +3362,12 @@ function reconnectPassiveEffects( // "Atomic" effects are ones that need to fire on every commit, // even during pre-rendering. An example is updating the reference // count on cache instances. - // TODO: Not yet implemented - // recursivelyTraverseAtomicPassiveEffects( - // finishedRoot, - // finishedWork, - // committedLanes, - // committedTransitions, - // ); + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); } } else { // Legacy Mode: Fire the effects even if the tree is hidden. @@ -3454,6 +3452,101 @@ function reconnectPassiveEffects( } } +function recursivelyTraverseAtomicPassiveEffects( + finishedRoot: FiberRoot, + parentFiber: Fiber, + committedLanes: Lanes, + committedTransitions: Array | null, +) { + // "Atomic" effects are ones that need to fire on every commit, even during + // pre-rendering. We call this function when traversing a hidden tree whose + // regular effects are currently disconnected. + const prevDebugFiber = getCurrentDebugFiberInDEV(); + // TODO: Add special flag for atomic effects + if (parentFiber.subtreeFlags & PassiveMask) { + let child = parentFiber.child; + while (child !== null) { + setCurrentDebugFiberInDEV(child); + commitAtomicPassiveEffects( + finishedRoot, + child, + committedLanes, + committedTransitions, + ); + child = child.sibling; + } + } + setCurrentDebugFiberInDEV(prevDebugFiber); +} + +function commitAtomicPassiveEffects( + finishedRoot: FiberRoot, + finishedWork: Fiber, + committedLanes: Lanes, + committedTransitions: Array | null, +) { + // "Atomic" effects are ones that need to fire on every commit, even during + // pre-rendering. We call this function when traversing a hidden tree whose + // regular effects are currently disconnected. + const flags = finishedWork.flags; + switch (finishedWork.tag) { + case OffscreenComponent: { + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); + if (flags & Passive) { + // TODO: Pass `current` as argument to this function + const current = finishedWork.alternate; + const instance: OffscreenInstance = finishedWork.stateNode; + commitOffscreenPassiveMountEffects(current, finishedWork, instance); + } + break; + } + case CacheComponent: { + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); + if (flags & Passive) { + // TODO: Pass `current` as argument to this function + const current = finishedWork.alternate; + commitCachePassiveMountEffect(current, finishedWork); + } + break; + } + case TracingMarkerComponent: { + if (enableTransitionTracing) { + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); + if (flags & Passive) { + commitTracingMarkerPassiveMountEffect(finishedWork); + } + break; + } + // Intentional fallthrough to next branch + } + // eslint-disable-next-line-no-fallthrough + default: { + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); + break; + } + } +} + export function commitPassiveUnmountEffects(finishedWork: Fiber): void { setCurrentDebugFiberInDEV(finishedWork); commitPassiveUnmountOnFiber(finishedWork); diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 97cb9c2808e322..bd16246302de81 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -3181,13 +3181,12 @@ function commitPassiveMountOnFiber( // "Atomic" effects are ones that need to fire on every commit, // even during pre-rendering. An example is updating the reference // count on cache instances. - // TODO: Not yet implemented - // recursivelyTraverseAtomicPassiveEffects( - // finishedRoot, - // finishedWork, - // committedLanes, - // committedTransitions, - // ); + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); } } else { // Legacy Mode: Fire the effects even if the tree is hidden. @@ -3363,13 +3362,12 @@ function reconnectPassiveEffects( // "Atomic" effects are ones that need to fire on every commit, // even during pre-rendering. An example is updating the reference // count on cache instances. - // TODO: Not yet implemented - // recursivelyTraverseAtomicPassiveEffects( - // finishedRoot, - // finishedWork, - // committedLanes, - // committedTransitions, - // ); + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); } } else { // Legacy Mode: Fire the effects even if the tree is hidden. @@ -3454,6 +3452,101 @@ function reconnectPassiveEffects( } } +function recursivelyTraverseAtomicPassiveEffects( + finishedRoot: FiberRoot, + parentFiber: Fiber, + committedLanes: Lanes, + committedTransitions: Array | null, +) { + // "Atomic" effects are ones that need to fire on every commit, even during + // pre-rendering. We call this function when traversing a hidden tree whose + // regular effects are currently disconnected. + const prevDebugFiber = getCurrentDebugFiberInDEV(); + // TODO: Add special flag for atomic effects + if (parentFiber.subtreeFlags & PassiveMask) { + let child = parentFiber.child; + while (child !== null) { + setCurrentDebugFiberInDEV(child); + commitAtomicPassiveEffects( + finishedRoot, + child, + committedLanes, + committedTransitions, + ); + child = child.sibling; + } + } + setCurrentDebugFiberInDEV(prevDebugFiber); +} + +function commitAtomicPassiveEffects( + finishedRoot: FiberRoot, + finishedWork: Fiber, + committedLanes: Lanes, + committedTransitions: Array | null, +) { + // "Atomic" effects are ones that need to fire on every commit, even during + // pre-rendering. We call this function when traversing a hidden tree whose + // regular effects are currently disconnected. + const flags = finishedWork.flags; + switch (finishedWork.tag) { + case OffscreenComponent: { + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); + if (flags & Passive) { + // TODO: Pass `current` as argument to this function + const current = finishedWork.alternate; + const instance: OffscreenInstance = finishedWork.stateNode; + commitOffscreenPassiveMountEffects(current, finishedWork, instance); + } + break; + } + case CacheComponent: { + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); + if (flags & Passive) { + // TODO: Pass `current` as argument to this function + const current = finishedWork.alternate; + commitCachePassiveMountEffect(current, finishedWork); + } + break; + } + case TracingMarkerComponent: { + if (enableTransitionTracing) { + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); + if (flags & Passive) { + commitTracingMarkerPassiveMountEffect(finishedWork); + } + break; + } + // Intentional fallthrough to next branch + } + // eslint-disable-next-line-no-fallthrough + default: { + recursivelyTraverseAtomicPassiveEffects( + finishedRoot, + finishedWork, + committedLanes, + committedTransitions, + ); + break; + } + } +} + export function commitPassiveUnmountEffects(finishedWork: Fiber): void { setCurrentDebugFiberInDEV(finishedWork); commitPassiveUnmountOnFiber(finishedWork); diff --git a/packages/react-reconciler/src/__tests__/ReactCache-test.js b/packages/react-reconciler/src/__tests__/ReactCache-test.js index 804486721f9cf5..970bf940f27cd6 100644 --- a/packages/react-reconciler/src/__tests__/ReactCache-test.js +++ b/packages/react-reconciler/src/__tests__/ReactCache-test.js @@ -6,6 +6,7 @@ let getCacheForType; let Scheduler; let act; let Suspense; +let Offscreen; let useCacheRefresh; let startTransition; let useState; @@ -23,6 +24,7 @@ describe('ReactCache', () => { Scheduler = require('scheduler'); act = require('jest-react').act; Suspense = React.Suspense; + Offscreen = React.unstable_Offscreen; getCacheSignal = React.unstable_getCacheSignal; getCacheForType = React.unstable_getCacheForType; useCacheRefresh = React.unstable_useCacheRefresh; @@ -1590,4 +1592,36 @@ describe('ReactCache', () => { ]); expect(root).toMatchRenderedOutput('Bye!'); }); + + // @gate enableOffscreen + // @gate enableCache + test('prerender a new cache boundary inside an Offscreen tree', async () => { + function App({prerenderMore}) { + return ( + +
+ {prerenderMore ? ( + + + + ) : null} +
+
+ ); + } + + const root = ReactNoop.createRoot(); + await act(async () => { + root.render(); + }); + expect(Scheduler).toHaveYielded([]); + expect(root).toMatchRenderedOutput(