diff --git a/fixtures/view-transition/src/components/Transitions.module.css b/fixtures/view-transition/src/components/Transitions.module.css index 8c7b66b446d44..0cfe870cfb6ff 100644 --- a/fixtures/view-transition/src/components/Transitions.module.css +++ b/fixtures/view-transition/src/components/Transitions.module.css @@ -56,10 +56,10 @@ } ::view-transition-new(.enter-slide-right):only-child { - animation: enter-slide-right ease-in 0.25s; + animation: enter-slide-right ease-in 0.25s forwards; } ::view-transition-old(.exit-slide-left):only-child { - animation: exit-slide-left ease-in 0.25s; + animation: exit-slide-left ease-in 0.25s forwards; } :root:active-view-transition-type(navigation-back) { diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index eb86ffea713fa..33786a41877b8 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -1516,7 +1516,7 @@ export function attach( currentRoot = rootInstance; unmountInstanceRecursively(rootInstance); rootToFiberInstanceMap.delete(root); - flushPendingEvents(root); + flushPendingEvents(); currentRoot = (null: any); }); @@ -1541,7 +1541,7 @@ export function attach( currentRoot = newRoot; setRootPseudoKey(currentRoot.id, root.current); mountFiberRecursively(root.current, false); - flushPendingEvents(root); + flushPendingEvents(); currentRoot = (null: any); }); @@ -2099,7 +2099,7 @@ export function attach( } } - function flushPendingEvents(root: Object): void { + function flushPendingEvents(): void { if (shouldBailoutWithPendingOperations()) { // If we aren't profiling, we can just bail out here. // No use sending an empty update over the bridge. @@ -2902,16 +2902,32 @@ export function attach( // Let's remove it from the parent SuspenseNode. const ioInfo = asyncInfo.awaited; const suspendedBySet = parentSuspenseNode.suspendedBy.get(ioInfo); + if ( suspendedBySet === undefined || !suspendedBySet.delete(instance) ) { - throw new Error( - 'We are cleaning up async info that was not on the parent Suspense boundary. ' + - 'This is a bug in React.', - ); + // A boundary can await the same IO multiple times. + // We still want to error if we're trying to remove IO that isn't present on + // this boundary so we need to check if we've already removed it. + // We're assuming previousSuspendedBy is a small array so this should be faster + // than allocating and maintaining a Set. + let alreadyRemovedIO = false; + for (let j = 0; j < i; j++) { + const removedIOInfo = previousSuspendedBy[j].awaited; + if (removedIOInfo === ioInfo) { + alreadyRemovedIO = true; + break; + } + } + if (!alreadyRemovedIO) { + throw new Error( + 'We are cleaning up async info that was not on the parent Suspense boundary. ' + + 'This is a bug in React.', + ); + } } - if (suspendedBySet.size === 0) { + if (suspendedBySet !== undefined && suspendedBySet.size === 0) { parentSuspenseNode.suspendedBy.delete(asyncInfo.awaited); } if ( @@ -5336,7 +5352,7 @@ export function attach( mountFiberRecursively(root.current, false); - flushPendingEvents(root); + flushPendingEvents(); needsToFlushComponentLogs = false; currentRoot = (null: any); @@ -5439,6 +5455,9 @@ export function attach( unmountInstanceRecursively(rootInstance); removeRootPseudoKey(currentRoot.id); rootToFiberInstanceMap.delete(root); + } else if (!prevWasMounted && !nextIsMounted) { + // We don't need this root anymore. + rootToFiberInstanceMap.delete(root); } if (isProfiling && isProfilingSupported) { @@ -5462,7 +5481,7 @@ export function attach( } // We're done here. - flushPendingEvents(root); + flushPendingEvents(); needsToFlushComponentLogs = false;