Skip to content

Commit

Permalink
insert empty text node during hydration
Browse files Browse the repository at this point in the history
  • Loading branch information
salazarm committed Nov 22, 2021
1 parent 149b420 commit f898b8c
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2958,4 +2958,44 @@ describe('ReactDOMServerPartialHydration', () => {
expect(ref.current).toBe(span);
expect(ref.current.innerHTML).toBe('Hidden child');
});

function itHydratesWithoutMismatch(msg, App) {
it(msg + ' without mismatch', () => {
const container = document.createElement('div');
document.body.appendChild(container);
const finalHTML = ReactDOMServer.renderToString(<App />);
container.innerHTML = finalHTML;

ReactDOM.hydrateRoot(container, <App />);
Scheduler.unstable_flushAll();
});
}

itHydratesWithoutMismatch('can hydrate empty string ', function App() {
return (
<div>
<div id="test">Test</div>
{'' && <div>Test</div>}
<div>Test</div>
</div>
);
});

itHydratesWithoutMismatch('can hydrate empty string simple', function App() {
return '';
});
itHydratesWithoutMismatch('can hydrate empty string simple', function App() {
return (
<>
{''}
{'sup'}
</>
);
});
itHydratesWithoutMismatch(
'can hydrate empty string without mismatch simple 2',
function App() {
return <Suspense>{'' && false}</Suspense>;
},
);
});
23 changes: 21 additions & 2 deletions packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -692,14 +692,33 @@ export function canHydrateTextInstance(
instance: HydratableInstance,
text: string,
): null | TextInstance {
if (text === '' || instance.nodeType !== TEXT_NODE) {
// Empty strings are not parsed by HTML so there won't be a correct match here.
if (
(instance.textContent !== '' && text === '') ||
instance.nodeType !== TEXT_NODE
) {
return null;
}
// This has now been refined to a text node.
return ((instance: any): TextInstance);
}

export function insertMissingEmptyTextNode(
instance: null | HydratableInstance,
parent: null | HydratableInstance,
): null | HydratableInstance {
const parentNode = instance ? instance.parentNode : parent;
if (parentNode) {
const textNode = document.createTextNode('');
if (instance) {
parentNode.insertBefore(textNode, instance);
} else {
parentNode.appendChild(textNode);
}
return (textNode: TextInstance);
}
return null;
}

export function canHydrateSuspenseInstance(
instance: HydratableInstance,
): null | SuspenseInstance {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type SuspenseInstance = mixed;
export const supportsHydration = false;
export const canHydrateInstance = shim;
export const canHydrateTextInstance = shim;
export const insertMissingEmptyTextNode = shim;
export const canHydrateSuspenseInstance = shim;
export const isSuspenseInstancePending = shim;
export const isSuspenseInstanceFallback = shim;
Expand Down
39 changes: 39 additions & 0 deletions packages/react-reconciler/src/ReactFiberHydrationContext.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
HostComponent,
HostText,
HostRoot,
HostPortal,
SuspenseComponent,
} from './ReactWorkTags';
import {ChildDeletion, Placement, Hydrating} from './ReactFiberFlags';
Expand Down Expand Up @@ -61,6 +62,7 @@ import {
didNotFindHydratableInstance,
didNotFindHydratableTextInstance,
didNotFindHydratableSuspenseInstance,
insertMissingEmptyTextNode,
} from './ReactFiberHostConfig';
import {
enableClientRenderFallbackOnHydrationMismatch,
Expand Down Expand Up @@ -327,6 +329,36 @@ function tryHydrate(fiber, nextInstance) {
}
}

function tryHydrateEmptyTextNode(
fiber: Fiber,
nextInstance: null | HydratableInstance,
parentFiber: null | Fiber,
) {
if (
nextInstance &&
canHydrateTextInstance(nextInstance, fiber.pendingProps)
) {
return nextInstance;
} else {
if (!parentFiber) {
return null;
}
switch (parentFiber.tag) {
case HostRoot:
case HostPortal:
return insertMissingEmptyTextNode(
nextInstance,
parentFiber.stateNode.containerInfo,
);
case HostComponent:
return insertMissingEmptyTextNode(nextInstance, parentFiber.stateNode);
default:
// Recurse upwards to find parent host node for text node
return tryHydrateEmptyTextNode(fiber, nextInstance, parentFiber.return);
}
}
}

function throwOnHydrationMismatchIfConcurrentMode(fiber: Fiber) {
if (
enableClientRenderFallbackOnHydrationMismatch &&
Expand All @@ -342,6 +374,13 @@ function tryToClaimNextHydratableInstance(fiber: Fiber): void {
if (!isHydrating) {
return;
}
if (fiber.tag === HostText && fiber.pendingProps === '') {
nextHydratableInstance = tryHydrateEmptyTextNode(
fiber,
nextHydratableInstance,
hydrationParentFiber,
);
}
let nextInstance = nextHydratableInstance;
if (!nextInstance) {
throwOnHydrationMismatchIfConcurrentMode(fiber);
Expand Down
39 changes: 39 additions & 0 deletions packages/react-reconciler/src/ReactFiberHydrationContext.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
HostComponent,
HostText,
HostRoot,
HostPortal,
SuspenseComponent,
} from './ReactWorkTags';
import {ChildDeletion, Placement, Hydrating} from './ReactFiberFlags';
Expand Down Expand Up @@ -61,6 +62,7 @@ import {
didNotFindHydratableInstance,
didNotFindHydratableTextInstance,
didNotFindHydratableSuspenseInstance,
insertMissingEmptyTextNode,
} from './ReactFiberHostConfig';
import {
enableClientRenderFallbackOnHydrationMismatch,
Expand Down Expand Up @@ -327,6 +329,36 @@ function tryHydrate(fiber, nextInstance) {
}
}

function tryHydrateEmptyTextNode(
fiber: Fiber,
nextInstance: null | HydratableInstance,
parentFiber: null | Fiber,
) {
if (
nextInstance &&
canHydrateTextInstance(nextInstance, fiber.pendingProps)
) {
return nextInstance;
} else {
if (!parentFiber) {
return null;
}
switch (parentFiber.tag) {
case HostRoot:
case HostPortal:
return insertMissingEmptyTextNode(
nextInstance,
parentFiber.stateNode.containerInfo,
);
case HostComponent:
return insertMissingEmptyTextNode(nextInstance, parentFiber.stateNode);
default:
// Recurse upwards to find parent host node for text node
return tryHydrateEmptyTextNode(fiber, nextInstance, parentFiber.return);
}
}
}

function throwOnHydrationMismatchIfConcurrentMode(fiber: Fiber) {
if (
enableClientRenderFallbackOnHydrationMismatch &&
Expand All @@ -342,6 +374,13 @@ function tryToClaimNextHydratableInstance(fiber: Fiber): void {
if (!isHydrating) {
return;
}
if (fiber.tag === HostText && fiber.pendingProps === '') {
nextHydratableInstance = tryHydrateEmptyTextNode(
fiber,
nextHydratableInstance,
hydrationParentFiber,
);
}
let nextInstance = nextHydratableInstance;
if (!nextInstance) {
throwOnHydrationMismatchIfConcurrentMode(fiber);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ export const cloneHiddenTextInstance = $$$hostConfig.cloneHiddenTextInstance;
// -------------------
export const canHydrateInstance = $$$hostConfig.canHydrateInstance;
export const canHydrateTextInstance = $$$hostConfig.canHydrateTextInstance;
export const insertMissingEmptyTextNode =
$$$hostConfig.insertMissingEmptyTextNode;
export const canHydrateSuspenseInstance =
$$$hostConfig.canHydrateSuspenseInstance;
export const isSuspenseInstancePending =
Expand Down

0 comments on commit f898b8c

Please sign in to comment.