From 547f9b93d2786c8542e4bc0862921838b90335a6 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 5 Jun 2021 15:15:11 +0100 Subject: [PATCH 1/6] Add a failing test for Suspense hydration --- ...DOMServerPartialHydration-test.internal.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index b1a88868b33..93c032ac79d 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -160,6 +160,59 @@ describe('ReactDOMServerPartialHydration', () => { expect(ref.current).toBe(span); }); + it('can hydrate siblings of a suspended component without errors', async () => { + let suspend = false; + let resolve; + const promise = new Promise(resolvePromise => (resolve = resolvePromise)); + function Child() { + if (suspend) { + throw promise; + } else { + return 'Hello'; + } + } + + function App() { + return ( + + + + + + + ); + } + + // First we render the final HTML. With the streaming renderer + // this may have suspense points on the server but here we want + // to test the completed HTML. Don't suspend on the server. + suspend = false; + const finalHTML = ReactDOMServer.renderToString(); + + const container = document.createElement('div'); + container.innerHTML = finalHTML; + expect(container.textContent).toBe('HelloHello'); + + // On the client we don't have all data yet but we want to start + // hydrating anyway. + suspend = true; + const root = ReactDOM.createRoot(container, {hydrate: true}); + root.render(); + Scheduler.unstable_flushAll(); + jest.runAllTimers(); + // Expect the server-generated HTML to stay intact. + expect(container.textContent).toBe('HelloHello'); + + // Resolving the promise should continue hydration + suspend = false; + resolve(); + await promise; + Scheduler.unstable_flushAll(); + jest.runAllTimers(); + // Hydration should not change anything. + expect(container.textContent).toBe('HelloHello'); + }); + it('calls the hydration callbacks after hydration or deletion', async () => { let suspend = false; let resolve; From 3323e3423807521760b7a419704a215c96d02666 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 18 Oct 2021 17:20:58 -0400 Subject: [PATCH 2/6] Include salazarm's changes to the test --- .../ReactDOMServerPartialHydration-test.internal.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 93c032ac79d..9578ec4715f 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -177,7 +177,7 @@ describe('ReactDOMServerPartialHydration', () => { - +
Hello
); @@ -196,10 +196,10 @@ describe('ReactDOMServerPartialHydration', () => { // On the client we don't have all data yet but we want to start // hydrating anyway. suspend = true; - const root = ReactDOM.createRoot(container, {hydrate: true}); - root.render(); + ReactDOM.hydrateRoot(container, ); Scheduler.unstable_flushAll(); jest.runAllTimers(); + // Expect the server-generated HTML to stay intact. expect(container.textContent).toBe('HelloHello'); From 561ad73af30cffe7fd04a8cd8effd184d6760982 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 18 Oct 2021 17:31:07 -0400 Subject: [PATCH 3/6] The hydration parent of a suspense boundary should be the boundary itself This eventually got set when we popped back out of its children but it doesn't start out that way. This fixes it so that the boundary parent is always the suspense boundary. --- packages/react-reconciler/src/ReactFiberHydrationContext.new.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js index f694d8407ec..1369badcc77 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js @@ -93,7 +93,7 @@ function reenterHydrationStateFromDehydratedSuspenseInstance( return false; } nextHydratableInstance = getNextHydratableSibling(suspenseInstance); - popToNextHostParent(fiber); + hydrationParentFiber = fiber; isHydrating = true; return true; } From eb4bf1896a2e15bd5ab0523f51e88b27a6fc6d22 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 18 Oct 2021 18:38:43 -0400 Subject: [PATCH 4/6] We now need to log errors with a suspense boundary as a parent For now, we just log this with commentNode.parentNode as the parent for purposes of the error message. --- ...DOMServerPartialHydration-test.internal.js | 7 +- .../src/client/ReactDOMHostConfig.js | 60 +++++++++++++++-- .../ReactFiberHostConfigWithNoHydration.js | 12 ++-- .../src/ReactFiberHydrationContext.new.js | 66 ++++++++++++++++--- .../src/forks/ReactFiberHostConfig.custom.js | 24 ++++--- 5 files changed, 144 insertions(+), 25 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 9578ec4715f..4b647e00032 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -197,7 +197,12 @@ describe('ReactDOMServerPartialHydration', () => { // hydrating anyway. suspend = true; ReactDOM.hydrateRoot(container, ); - Scheduler.unstable_flushAll(); + expect(() => { + Scheduler.unstable_flushAll(); + }).toErrorDev( + // TODO: This error should not be logged in this case. It's a false positive. + 'Did not expect server HTML to contain the text node "Hello" in
.', + ); jest.runAllTimers(); // Expect the server-generated HTML to stay intact. diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index 74877c1591e..9f7ed4b06ef 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -917,7 +917,7 @@ export function didNotMatchHydratedTextInstance( } } -export function didNotHydrateContainerInstance( +export function didNotHydrateInstanceWithinContainer( parentContainer: Container, instance: HydratableInstance, ) { @@ -932,6 +932,25 @@ export function didNotHydrateContainerInstance( } } +export function didNotHydrateInstanceWithinSuspenseInstance( + parentInstance: SuspenseInstance, + instance: HydratableInstance, +) { + if (__DEV__) { + // $FlowFixMe: Only Element or Document can be parent nodes. + const parentNode: Element | Document | null = parentInstance.parentNode; + if (parentNode !== null) { + if (instance.nodeType === ELEMENT_NODE) { + warnForDeletedHydratableElement(parentNode, (instance: any)); + } else if (instance.nodeType === COMMENT_NODE) { + // TODO: warnForDeletedHydratableSuspenseBoundary + } else { + warnForDeletedHydratableText(parentNode, (instance: any)); + } + } + } +} + export function didNotHydrateInstance( parentType: string, parentProps: Props, @@ -949,7 +968,7 @@ export function didNotHydrateInstance( } } -export function didNotFindHydratableContainerInstance( +export function didNotFindHydratableInstanceWithinContainer( parentContainer: Container, type: string, props: Props, @@ -959,7 +978,7 @@ export function didNotFindHydratableContainerInstance( } } -export function didNotFindHydratableContainerTextInstance( +export function didNotFindHydratableTextInstanceWithinContainer( parentContainer: Container, text: string, ) { @@ -968,7 +987,7 @@ export function didNotFindHydratableContainerTextInstance( } } -export function didNotFindHydratableContainerSuspenseInstance( +export function didNotFindHydratableSuspenseInstanceWithinContainer( parentContainer: Container, ) { if (__DEV__) { @@ -976,6 +995,39 @@ export function didNotFindHydratableContainerSuspenseInstance( } } +export function didNotFindHydratableInstanceWithinSuspenseInstance( + parentInstance: SuspenseInstance, + type: string, + props: Props, +) { + if (__DEV__) { + // $FlowFixMe: Only Element or Document can be parent nodes. + const parentNode: Element | Document | null = parentInstance.parentNode; + if (parentNode !== null) + warnForInsertedHydratedElement(parentNode, type, props); + } +} + +export function didNotFindHydratableTextInstanceWithinSuspenseInstance( + parentInstance: SuspenseInstance, + text: string, +) { + if (__DEV__) { + // $FlowFixMe: Only Element or Document can be parent nodes. + const parentNode: Element | Document | null = parentInstance.parentNode; + if (parentNode !== null) warnForInsertedHydratedText(parentNode, text); + } +} + +export function didNotFindHydratableSuspenseInstanceWithinSuspenseInstance( + parentInstance: SuspenseInstance, +) { + if (__DEV__) { + // const parentNode: Element | Document | null = parentInstance.parentNode; + // TODO: warnForInsertedHydratedSuspense(parentNode); + } +} + export function didNotFindHydratableInstance( parentType: string, parentProps: Props, diff --git a/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js b/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js index b95fd52d189..a6889a6f303 100644 --- a/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js +++ b/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js @@ -40,11 +40,15 @@ export const clearSuspenseBoundaryFromContainer = shim; export const shouldDeleteUnhydratedTailInstances = shim; export const didNotMatchHydratedContainerTextInstance = shim; export const didNotMatchHydratedTextInstance = shim; -export const didNotHydrateContainerInstance = shim; +export const didNotHydrateInstanceWithinContainer = shim; +export const didNotHydrateInstanceWithinSuspenseInstance = shim; export const didNotHydrateInstance = shim; -export const didNotFindHydratableContainerInstance = shim; -export const didNotFindHydratableContainerTextInstance = shim; -export const didNotFindHydratableContainerSuspenseInstance = shim; +export const didNotFindHydratableInstanceWithinContainer = shim; +export const didNotFindHydratableTextInstanceWithinContainer = shim; +export const didNotFindHydratableSuspenseInstanceWithinContainer = shim; +export const didNotFindHydratableInstanceWithinSuspenseInstance = shim; +export const didNotFindHydratableTextInstanceWithinSuspenseInstance = shim; +export const didNotFindHydratableSuspenseInstanceWithinSuspenseInstance = shim; export const didNotFindHydratableInstance = shim; export const didNotFindHydratableTextInstance = shim; export const didNotFindHydratableSuspenseInstance = shim; diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js index 1369badcc77..26ce593f850 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js @@ -45,11 +45,15 @@ import { shouldDeleteUnhydratedTailInstances, didNotMatchHydratedContainerTextInstance, didNotMatchHydratedTextInstance, - didNotHydrateContainerInstance, + didNotHydrateInstanceWithinContainer, + didNotHydrateInstanceWithinSuspenseInstance, didNotHydrateInstance, - didNotFindHydratableContainerInstance, - didNotFindHydratableContainerTextInstance, - didNotFindHydratableContainerSuspenseInstance, + didNotFindHydratableInstanceWithinContainer, + didNotFindHydratableTextInstanceWithinContainer, + didNotFindHydratableSuspenseInstanceWithinContainer, + didNotFindHydratableInstanceWithinSuspenseInstance, + didNotFindHydratableTextInstanceWithinSuspenseInstance, + didNotFindHydratableSuspenseInstanceWithinSuspenseInstance, didNotFindHydratableInstance, didNotFindHydratableTextInstance, didNotFindHydratableSuspenseInstance, @@ -105,7 +109,7 @@ function deleteHydratableInstance( if (__DEV__) { switch (returnFiber.tag) { case HostRoot: - didNotHydrateContainerInstance( + didNotHydrateInstanceWithinContainer( returnFiber.stateNode.containerInfo, instance, ); @@ -118,6 +122,14 @@ function deleteHydratableInstance( instance, ); break; + case SuspenseComponent: + const suspenseState: SuspenseState = returnFiber.memoizedState; + if (suspenseState.dehydrated !== null) + didNotHydrateInstanceWithinSuspenseInstance( + suspenseState.dehydrated, + instance, + ); + break; } } @@ -144,14 +156,23 @@ function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) { case HostComponent: const type = fiber.type; const props = fiber.pendingProps; - didNotFindHydratableContainerInstance(parentContainer, type, props); + didNotFindHydratableInstanceWithinContainer( + parentContainer, + type, + props, + ); break; case HostText: const text = fiber.pendingProps; - didNotFindHydratableContainerTextInstance(parentContainer, text); + didNotFindHydratableTextInstanceWithinContainer( + parentContainer, + text, + ); break; case SuspenseComponent: - didNotFindHydratableContainerSuspenseInstance(parentContainer); + didNotFindHydratableSuspenseInstanceWithinContainer( + parentContainer, + ); break; } break; @@ -191,6 +212,35 @@ function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) { } break; } + case SuspenseComponent: { + const suspenseState: SuspenseState = returnFiber.memoizedState; + const parentInstance = suspenseState.dehydrated; + if (parentInstance !== null) + switch (fiber.tag) { + case HostComponent: + const type = fiber.type; + const props = fiber.pendingProps; + didNotFindHydratableInstanceWithinSuspenseInstance( + parentInstance, + type, + props, + ); + break; + case HostText: + const text = fiber.pendingProps; + didNotFindHydratableTextInstanceWithinSuspenseInstance( + parentInstance, + text, + ); + break; + case SuspenseComponent: + didNotFindHydratableSuspenseInstanceWithinSuspenseInstance( + parentInstance, + ); + break; + } + break; + } default: return; } diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js index 1cc10a78cb0..46fae95edb3 100644 --- a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js +++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js @@ -167,15 +167,23 @@ export const didNotMatchHydratedContainerTextInstance = $$$hostConfig.didNotMatchHydratedContainerTextInstance; export const didNotMatchHydratedTextInstance = $$$hostConfig.didNotMatchHydratedTextInstance; -export const didNotHydrateContainerInstance = - $$$hostConfig.didNotHydrateContainerInstance; +export const didNotHydrateInstanceWithinContainer = + $$$hostConfig.didNotHydrateInstanceWithinContainer; +export const didNotHydrateInstanceWithinSuspenseInstance = + $$$hostConfig.didNotHydrateInstanceWithinSuspenseInstance; export const didNotHydrateInstance = $$$hostConfig.didNotHydrateInstance; -export const didNotFindHydratableContainerInstance = - $$$hostConfig.didNotFindHydratableContainerInstance; -export const didNotFindHydratableContainerTextInstance = - $$$hostConfig.didNotFindHydratableContainerTextInstance; -export const didNotFindHydratableContainerSuspenseInstance = - $$$hostConfig.didNotFindHydratableContainerSuspenseInstance; +export const didNotFindHydratableInstanceWithinContainer = + $$$hostConfig.didNotFindHydratableInstanceWithinContainer; +export const didNotFindHydratableTextInstanceWithinContainer = + $$$hostConfig.didNotFindHydratableTextInstanceWithinContainer; +export const didNotFindHydratableSuspenseInstanceWithinContainer = + $$$hostConfig.didNotFindHydratableSuspenseInstanceWithinContainer; +export const didNotFindHydratableInstanceWithinSuspenseInstance = + $$$hostConfig.didNotFindHydratableInstanceWithinSuspenseInstance; +export const didNotFindHydratableTextInstanceWithinSuspenseInstance = + $$$hostConfig.didNotFindHydratableTextInstanceWithinSuspenseInstance; +export const didNotFindHydratableSuspenseInstanceWithinSuspenseInstance = + $$$hostConfig.didNotFindHydratableSuspenseInstanceWithinSuspenseInstance; export const didNotFindHydratableInstance = $$$hostConfig.didNotFindHydratableInstance; export const didNotFindHydratableTextInstance = From aaaf98411010c4a7eb018aad3b017f0a71d49484 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 18 Oct 2021 19:32:36 -0400 Subject: [PATCH 5/6] Make a special getFirstHydratableChildWithinSuspenseInstance We currently call getNextHydratableSibling but conceptually it's the child of the boundary. It just happens to be that when we use comment nodes, we need to call nextSibling in the DOM. This makes this separation a bit clearer. --- .../react-dom/src/client/ReactDOMHostConfig.js | 14 +++++++++++++- .../src/ReactFiberHostConfigWithNoHydration.js | 2 ++ .../src/ReactFiberHydrationContext.new.js | 12 +++++++++--- .../src/forks/ReactFiberHostConfig.custom.js | 4 ++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js index 9f7ed4b06ef..79b534d1e18 100644 --- a/packages/react-dom/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom/src/client/ReactDOMHostConfig.js @@ -764,11 +764,23 @@ export function getNextHydratableSibling( } export function getFirstHydratableChild( - parentInstance: Container | Instance, + parentInstance: Instance, ): null | HydratableInstance { return getNextHydratable(parentInstance.firstChild); } +export function getFirstHydratableChildWithinContainer( + parentContainer: Container, +): null | HydratableInstance { + return getNextHydratable(parentContainer.firstChild); +} + +export function getFirstHydratableChildWithinSuspenseInstance( + parentInstance: SuspenseInstance, +): null | HydratableInstance { + return getNextHydratable(parentInstance.nextSibling); +} + export function hydrateInstance( instance: Instance, type: string, diff --git a/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js b/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js index a6889a6f303..1392cf8a260 100644 --- a/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js +++ b/packages/react-reconciler/src/ReactFiberHostConfigWithNoHydration.js @@ -29,6 +29,8 @@ export const isSuspenseInstanceFallback = shim; export const registerSuspenseInstanceRetry = shim; export const getNextHydratableSibling = shim; export const getFirstHydratableChild = shim; +export const getFirstHydratableChildWithinContainer = shim; +export const getFirstHydratableChildWithinSuspenseInstance = shim; export const hydrateInstance = shim; export const hydrateTextInstance = shim; export const hydrateSuspenseInstance = shim; diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js index 26ce593f850..6aad7f03339 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.new.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.new.js @@ -38,6 +38,8 @@ import { canHydrateSuspenseInstance, getNextHydratableSibling, getFirstHydratableChild, + getFirstHydratableChildWithinContainer, + getFirstHydratableChildWithinSuspenseInstance, hydrateInstance, hydrateTextInstance, hydrateSuspenseInstance, @@ -82,8 +84,10 @@ function enterHydrationState(fiber: Fiber): boolean { return false; } - const parentInstance = fiber.stateNode.containerInfo; - nextHydratableInstance = getFirstHydratableChild(parentInstance); + const parentInstance: Container = fiber.stateNode.containerInfo; + nextHydratableInstance = getFirstHydratableChildWithinContainer( + parentInstance, + ); hydrationParentFiber = fiber; isHydrating = true; return true; @@ -96,7 +100,9 @@ function reenterHydrationStateFromDehydratedSuspenseInstance( if (!supportsHydration) { return false; } - nextHydratableInstance = getNextHydratableSibling(suspenseInstance); + nextHydratableInstance = getFirstHydratableChildWithinSuspenseInstance( + suspenseInstance, + ); hydrationParentFiber = fiber; isHydrating = true; return true; diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js index 46fae95edb3..29d4fd7e798 100644 --- a/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js +++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js @@ -150,6 +150,10 @@ export const registerSuspenseInstanceRetry = $$$hostConfig.registerSuspenseInstanceRetry; export const getNextHydratableSibling = $$$hostConfig.getNextHydratableSibling; export const getFirstHydratableChild = $$$hostConfig.getFirstHydratableChild; +export const getFirstHydratableChildWithinContainer = + $$$hostConfig.getFirstHydratableChildWithinContainer; +export const getFirstHydratableChildWithinSuspenseInstance = + $$$hostConfig.getFirstHydratableChildWithinSuspenseInstance; export const hydrateInstance = $$$hostConfig.hydrateInstance; export const hydrateTextInstance = $$$hostConfig.hydrateTextInstance; export const hydrateSuspenseInstance = $$$hostConfig.hydrateSuspenseInstance; From 593e5d1c996ebfb3f4252cfbc5b9e379ee542c41 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 18 Oct 2021 19:35:22 -0400 Subject: [PATCH 6/6] Sync old fork --- .../src/ReactFiberHydrationContext.old.js | 80 ++++++++++++++++--- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js index 3918c014ca1..fd0dd8e99a5 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationContext.old.js +++ b/packages/react-reconciler/src/ReactFiberHydrationContext.old.js @@ -38,6 +38,8 @@ import { canHydrateSuspenseInstance, getNextHydratableSibling, getFirstHydratableChild, + getFirstHydratableChildWithinContainer, + getFirstHydratableChildWithinSuspenseInstance, hydrateInstance, hydrateTextInstance, hydrateSuspenseInstance, @@ -45,11 +47,15 @@ import { shouldDeleteUnhydratedTailInstances, didNotMatchHydratedContainerTextInstance, didNotMatchHydratedTextInstance, - didNotHydrateContainerInstance, + didNotHydrateInstanceWithinContainer, + didNotHydrateInstanceWithinSuspenseInstance, didNotHydrateInstance, - didNotFindHydratableContainerInstance, - didNotFindHydratableContainerTextInstance, - didNotFindHydratableContainerSuspenseInstance, + didNotFindHydratableInstanceWithinContainer, + didNotFindHydratableTextInstanceWithinContainer, + didNotFindHydratableSuspenseInstanceWithinContainer, + didNotFindHydratableInstanceWithinSuspenseInstance, + didNotFindHydratableTextInstanceWithinSuspenseInstance, + didNotFindHydratableSuspenseInstanceWithinSuspenseInstance, didNotFindHydratableInstance, didNotFindHydratableTextInstance, didNotFindHydratableSuspenseInstance, @@ -78,8 +84,10 @@ function enterHydrationState(fiber: Fiber): boolean { return false; } - const parentInstance = fiber.stateNode.containerInfo; - nextHydratableInstance = getFirstHydratableChild(parentInstance); + const parentInstance: Container = fiber.stateNode.containerInfo; + nextHydratableInstance = getFirstHydratableChildWithinContainer( + parentInstance, + ); hydrationParentFiber = fiber; isHydrating = true; return true; @@ -92,8 +100,10 @@ function reenterHydrationStateFromDehydratedSuspenseInstance( if (!supportsHydration) { return false; } - nextHydratableInstance = getNextHydratableSibling(suspenseInstance); - popToNextHostParent(fiber); + nextHydratableInstance = getFirstHydratableChildWithinSuspenseInstance( + suspenseInstance, + ); + hydrationParentFiber = fiber; isHydrating = true; return true; } @@ -105,7 +115,7 @@ function deleteHydratableInstance( if (__DEV__) { switch (returnFiber.tag) { case HostRoot: - didNotHydrateContainerInstance( + didNotHydrateInstanceWithinContainer( returnFiber.stateNode.containerInfo, instance, ); @@ -118,6 +128,14 @@ function deleteHydratableInstance( instance, ); break; + case SuspenseComponent: + const suspenseState: SuspenseState = returnFiber.memoizedState; + if (suspenseState.dehydrated !== null) + didNotHydrateInstanceWithinSuspenseInstance( + suspenseState.dehydrated, + instance, + ); + break; } } @@ -144,14 +162,23 @@ function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) { case HostComponent: const type = fiber.type; const props = fiber.pendingProps; - didNotFindHydratableContainerInstance(parentContainer, type, props); + didNotFindHydratableInstanceWithinContainer( + parentContainer, + type, + props, + ); break; case HostText: const text = fiber.pendingProps; - didNotFindHydratableContainerTextInstance(parentContainer, text); + didNotFindHydratableTextInstanceWithinContainer( + parentContainer, + text, + ); break; case SuspenseComponent: - didNotFindHydratableContainerSuspenseInstance(parentContainer); + didNotFindHydratableSuspenseInstanceWithinContainer( + parentContainer, + ); break; } break; @@ -191,6 +218,35 @@ function insertNonHydratedInstance(returnFiber: Fiber, fiber: Fiber) { } break; } + case SuspenseComponent: { + const suspenseState: SuspenseState = returnFiber.memoizedState; + const parentInstance = suspenseState.dehydrated; + if (parentInstance !== null) + switch (fiber.tag) { + case HostComponent: + const type = fiber.type; + const props = fiber.pendingProps; + didNotFindHydratableInstanceWithinSuspenseInstance( + parentInstance, + type, + props, + ); + break; + case HostText: + const text = fiber.pendingProps; + didNotFindHydratableTextInstanceWithinSuspenseInstance( + parentInstance, + text, + ); + break; + case SuspenseComponent: + didNotFindHydratableSuspenseInstanceWithinSuspenseInstance( + parentInstance, + ); + break; + } + break; + } default: return; }