diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 975313a99f8b1..2e76e5188ec5f 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -3543,6 +3543,12 @@ function updateViewTransition( current === null ? ViewTransitionNamedMount | ViewTransitionNamedStatic : ViewTransitionNamedStatic; + } else { + // The server may have used useId to auto-assign a generated name for this boundary. + // We push a materialization to ensure child ids line up with the server. + if (getIsHydrating()) { + pushMaterializedTreeId(workInProgress); + } } if (__DEV__) { // $FlowFixMe[prop-missing] diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 03d78545d4ea5..e21271f3b3eea 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -210,6 +210,7 @@ import { TransitionRoot, TransitionTracingMarker, } from './ReactFiberTracingMarkerComponent'; +import {getViewTransitionClassName} from './ReactFiberViewTransitionComponent'; import { commitHookLayoutEffects, commitHookLayoutUnmountEffects, @@ -303,6 +304,7 @@ export let shouldFireAfterActiveInstanceBlur: boolean = false; // Used during the commit phase to track whether a parent ViewTransition component // might have been affected by any mutations / relayouts below. let viewTransitionContextChanged: boolean = false; +let inUpdateViewTransition: boolean = false; let rootViewTransitionAffected: boolean = false; function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean { @@ -1937,6 +1939,7 @@ export function commitMutationEffects( inProgressRoot = root; rootViewTransitionAffected = false; + inUpdateViewTransition = false; resetComponentEffectTimers(); @@ -2299,7 +2302,7 @@ function commitMutationEffectsOnFiber( recursivelyTraverseMutationEffects(root, finishedWork, lanes); commitReconciliationEffects(finishedWork, lanes); } - if (viewTransitionMutationContext) { + if (viewTransitionMutationContext && inUpdateViewTransition) { // A Portal doesn't necessarily exist within the context of this subtree. // Ideally we would track which React ViewTransition component nests the container // but that's costly. Instead, we treat each Portal as if it's a new React root. @@ -2534,11 +2537,16 @@ function commitMutationEffectsOnFiber( } } const prevMutationContext = pushMutationContext(); - recursivelyTraverseMutationEffects(root, finishedWork, lanes); - commitReconciliationEffects(finishedWork, lanes); + const prevUpdate = inUpdateViewTransition; const isViewTransitionEligible = enableViewTransition && includesOnlyViewTransitionEligibleLanes(lanes); + const props = finishedWork.memoizedProps; + inUpdateViewTransition = + isViewTransitionEligible && + getViewTransitionClassName(props.default, props.update) !== 'none'; + recursivelyTraverseMutationEffects(root, finishedWork, lanes); + commitReconciliationEffects(finishedWork, lanes); if (isViewTransitionEligible) { if (current === null) { // This is a new mount. We should have handled this as part of the @@ -2551,6 +2559,7 @@ function commitMutationEffectsOnFiber( finishedWork.flags |= Update; } } + inUpdateViewTransition = prevUpdate; popMutationContext(prevMutationContext); break; } @@ -2763,6 +2772,8 @@ function commitAfterMutationEffectsOnFiber( // Ideally we would track which React ViewTransition component nests the container // but that's costly. Instead, we treat each Portal as if it's a new React root. // Therefore any leaked resize of a child could affect the root so the root should animate. + // We only do this if the Portal is inside a ViewTransition and it is not disabled + // with update="none". Otherwise the Portal is considered not animating. rootViewTransitionAffected = true; } viewTransitionContextChanged = prevContextChanged; diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index f86dc8c9ce020..4e8f4f86fdc7d 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -2273,7 +2273,23 @@ function renderViewTransition( ) { const prevKeyPath = task.keyPath; task.keyPath = keyPath; - renderNodeDestructive(request, task, props.children, -1); + if (props.name != null && props.name !== 'auto') { + renderNodeDestructive(request, task, props.children, -1); + } else { + // This will be auto-assigned a name which claims a "useId" slot. + // This component materialized an id. We treat this as its own level, with + // a single "child" slot. + const prevTreeContext = task.treeContext; + const totalChildren = 1; + const index = 0; + // Modify the id context. Because we'll need to reset this if something + // suspends or errors, we'll use the non-destructive render path. + task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index); + renderNode(request, task, props.children, -1); + // Like the other contexts, this does not need to be in a finally block + // because renderNode takes care of unwinding the stack. + task.treeContext = prevTreeContext; + } task.keyPath = prevKeyPath; }