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-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; }