Skip to content

Commit 46787fd

Browse files
committed
support hydration properly
1 parent 101c086 commit 46787fd

19 files changed

+155
-223
lines changed

packages/react-dom-bindings/src/client/ReactDOMComponent.js

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,8 @@ function setInitialDOMProperties(
312312
// textContent on a <textarea> will cause the placeholder to not
313313
// show within the <textarea> until it has been focused and blurred again.
314314
// https://github.com/facebook/react/issues/6731#issuecomment-254874553
315-
const canSetTextContent = tag !== 'textarea' || nextProp !== '';
315+
const canSetTextContent =
316+
tag !== 'body' && (tag !== 'textarea' || nextProp !== '');
316317
if (canSetTextContent) {
317318
setTextContent(domElement, nextProp);
318319
}
@@ -483,18 +484,6 @@ export function createTextNode(
483484
);
484485
}
485486

486-
export function resetProperties(
487-
domElement: Element,
488-
tag: string,
489-
rawProps: Object,
490-
): void {
491-
const attributes = domElement.attributes;
492-
while (attributes.length) {
493-
domElement.removeAttribute(attributes[0].name);
494-
}
495-
setInitialProperties(domElement, tag, rawProps);
496-
}
497-
498487
export function setInitialProperties(
499488
domElement: Element,
500489
tag: string,

packages/react-dom-bindings/src/client/ReactDOMComponentTree.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@ import {
3030
SuspenseComponent,
3131
} from 'react-reconciler/src/ReactWorkTags';
3232

33-
import {
34-
getParentSuspenseInstance,
35-
supportsSingletons,
36-
} from './ReactDOMHostConfig';
33+
import {getParentSuspenseInstance} from './ReactDOMHostConfig';
3734

3835
import {
3936
enableScopeAPI,
@@ -182,9 +179,7 @@ export function getInstanceFromNode(node: Node): Fiber | null {
182179
tag === SuspenseComponent ||
183180
tag === HostRoot ||
184181
(enableFloat ? tag === HostResource : false) ||
185-
(enableHostSingletons && supportsSingletons
186-
? tag === HostSingleton
187-
: false)
182+
(enableHostSingletons ? tag === HostSingleton : false)
188183
) {
189184
return inst;
190185
} else {
@@ -204,7 +199,7 @@ export function getNodeFromInstance(inst: Fiber): Instance | TextInstance {
204199
tag === HostComponent ||
205200
tag === HostText ||
206201
(enableFloat ? tag === HostResource : false) ||
207-
(enableHostSingletons && supportsSingletons ? tag === HostSingleton : false)
202+
(enableHostSingletons ? tag === HostSingleton : false)
208203
) {
209204
// In Fiber this, is just the state node right now. We assume it will be
210205
// a host component or host text.

packages/react-dom-bindings/src/client/ReactDOMHostConfig.js

Lines changed: 47 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
setInitialProperties,
3333
diffProperties,
3434
updateProperties,
35-
resetProperties,
3635
diffHydratedProperties,
3736
diffHydratedText,
3837
trapClickOnNonInteractiveElement,
@@ -124,7 +123,6 @@ export type Container =
124123
| interface extends Document {_reactRootContainer?: FiberRoot}
125124
| interface extends DocumentFragment {_reactRootContainer?: FiberRoot};
126125
export type Instance = Element;
127-
export type InstanceSibling = Node;
128126
export type TextInstance = Text;
129127
export interface SuspenseInstance extends Comment {
130128
_reactRetry?: () => void;
@@ -552,15 +550,15 @@ export function appendChildToContainer(
552550
export function insertBefore(
553551
parentInstance: Instance,
554552
child: Instance | TextInstance,
555-
beforeChild: InstanceSibling,
553+
beforeChild: Instance,
556554
): void {
557555
parentInstance.insertBefore(child, beforeChild);
558556
}
559557

560558
export function insertInContainerBefore(
561559
container: Container,
562560
child: Instance | TextInstance,
563-
beforeChild: InstanceSibling,
561+
beforeChild: Instance,
564562
): void {
565563
if (container.nodeType === COMMENT_NODE) {
566564
(container.parentNode: any).insertBefore(child, beforeChild);
@@ -717,15 +715,32 @@ export function clearContainer(container: Container): void {
717715
case 'html':
718716
case 'head':
719717
case 'body': {
720-
clearSingletonInstance(element);
721-
break;
718+
let node = element.firstChild;
719+
while (node) {
720+
const nextNode = node.nextSibling;
721+
const nodeName = node.nodeName;
722+
if (
723+
nodeName === 'HEAD' ||
724+
nodeName === 'BODY' ||
725+
nodeName === 'STYLE' ||
726+
(nodeName === 'LINK' &&
727+
((node: any): HTMLLinkElement).rel.toLowerCase() ===
728+
'stylesheet')
729+
) {
730+
// retain these nodes
731+
} else {
732+
element.removeChild(node);
733+
}
734+
node = nextNode;
735+
}
736+
return;
722737
}
723738
default: {
724739
element.textContent = '';
725740
}
726741
}
727742
}
728-
// Implicitly if the container is of type Document we rely on the Persistent HostComponent
743+
// Implicitly if the container is of type Document we rely on the HostSingleton
729744
// semantics to clear these nodes appropriately when being placed so no ahead of time
730745
// clearing is necessary
731746
} else {
@@ -1246,7 +1261,7 @@ export function errorHydratingContainer(parentContainer: Container): void {
12461261
}
12471262
}
12481263

1249-
export function acquireSingletonInstance(
1264+
export function resolveSingletonInstance(
12501265
type: string,
12511266
props: Props,
12521267
rootContainerInstance: Container,
@@ -1262,50 +1277,51 @@ export function acquireSingletonInstance(
12621277
const ownerDocument = getOwnerDocumentFromRootContainer(
12631278
rootContainerInstance,
12641279
);
1265-
// For the three persistent Host Components that exist in DOM it is necessary for there to
1266-
// always be a documentElement. With normal html parsing this will always be the case but
1267-
// with pathological manipulation the document can end up in a state where no documentElement
1268-
// exists. We create it here if missing so we can treat it as an invariant.
1269-
// It is important to note that this dom mutation and others in this function happen
1270-
// in render rather than commit. This is tolerable because they only happen in degenerate cases
1271-
let htmlElement = ownerDocument.documentElement;
1272-
if (!htmlElement) {
1273-
htmlElement = ownerDocument.appendChild(
1274-
ownerDocument.createElement('html'),
1275-
);
1276-
}
12771280
switch (type) {
12781281
case 'html': {
1279-
return htmlElement;
1282+
const documentElement = ownerDocument.documentElement;
1283+
if (!documentElement) {
1284+
throw new Error(
1285+
'React expected an <html> element (document.documentElement) to exist in the Document but one was' +
1286+
' not found. React never removes the documentElement for any Document it renders into so' +
1287+
' the cause is likely in some other script running on this page.',
1288+
);
1289+
}
1290+
return documentElement;
12801291
}
12811292
case 'head': {
1282-
let head = ownerDocument.head;
1293+
const head = ownerDocument.head;
12831294
if (!head) {
1284-
head = htmlElement.insertBefore(
1285-
ownerDocument.createElement('head'),
1286-
ownerDocument.firstChild,
1295+
throw new Error(
1296+
'React expected a <head> element (document.head) to exist in the Document but one was' +
1297+
' not found. React never removes the head for any Document it renders into so' +
1298+
' the cause is likely in some other script running on this page.',
12871299
);
12881300
}
12891301
// We ensure this exists just above
12901302
return head;
12911303
}
12921304
case 'body': {
1293-
let body = ownerDocument.body;
1305+
const body = ownerDocument.body;
12941306
if (!body) {
1295-
body = htmlElement.appendChild(ownerDocument.createElement('body'));
1307+
throw new Error(
1308+
'React expected a <body> element (document.body) to exist in the Document but one was' +
1309+
' not found. React never removes the body for any Document it renders into so' +
1310+
' the cause is likely in some other script running on this page.',
1311+
);
12961312
}
12971313
// We ensure this exists just above
12981314
return body;
12991315
}
13001316
default: {
13011317
throw new Error(
1302-
'acquireSingletonInstance was called with an element type that is not supported. This is a bug in React.',
1318+
'resolveSingletonInstance was called with an element type that is not supported. This is a bug in React.',
13031319
);
13041320
}
13051321
}
13061322
}
13071323

1308-
export function resetSingletonInstance(
1324+
export function acquireSingletonInstance(
13091325
type: string,
13101326
props: Props,
13111327
instance: Instance,
@@ -1320,51 +1336,17 @@ export function resetSingletonInstance(
13201336
}
13211337
default: {
13221338
console.error(
1323-
'resetSingletonInstance was called with an element type that is not supported. This is a bug in React.',
1339+
'acquireSingletonInstance was called with an element type that is not supported. This is a bug in React.',
13241340
);
13251341
}
13261342
}
13271343
}
13281344

1329-
clearSingletonInstance(instance);
1330-
resetProperties(instance, type, props);
1345+
setInitialProperties(instance, type, props);
13311346
precacheFiberNode(internalInstanceHandle, instance);
13321347
updateFiberProps(instance, props);
13331348
}
13341349

1335-
export function clearSingletonInstance(instance: Instance) {
1336-
const tagName = instance.tagName.toLowerCase();
1337-
switch (tagName) {
1338-
case 'html': {
1339-
return;
1340-
}
1341-
case 'head':
1342-
case 'body': {
1343-
let node = instance.firstChild;
1344-
while (node) {
1345-
const nextNode = node.nextSibling;
1346-
const nodeName = node.nodeName;
1347-
if (
1348-
nodeName === 'STYLE' ||
1349-
(nodeName === 'LINK' &&
1350-
((node: any): HTMLLinkElement).rel.toLowerCase() === 'stylesheet')
1351-
) {
1352-
// retain these nodes
1353-
} else {
1354-
instance.removeChild(node);
1355-
}
1356-
node = nextNode;
1357-
}
1358-
return;
1359-
}
1360-
default: {
1361-
throw new Error(
1362-
'clearSingletonInstance was called with an element type that is not supported. this is a bug in React.',
1363-
);
1364-
}
1365-
}
1366-
}
1367-
13681350
// -------------------
13691351
// Test Selectors
13701352
// -------------------

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4375,7 +4375,7 @@ describe('ReactDOMFizzServer', () => {
43754375
);
43764376
});
43774377

4378-
// @gate enableHostSingletons
4378+
// @gate enableFloat
43794379
it('holds back body and html closing tags (the postamble) until all pending tasks are completed', async () => {
43804380
const chunks = [];
43814381
writable.on('data', chunk => {

packages/react-dom/src/__tests__/ReactDOMRoot-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ describe('ReactDOMRoot', () => {
358358
);
359359
});
360360

361-
// @gate !__DEV__ || !enableHostSingletons
361+
// @gate !enableFloat && !enableHostSingletons
362362
it('warns if updating a root that has had its contents removed', async () => {
363363
const root = ReactDOMClient.createRoot(container);
364364
root.render(<div>Hi</div>);

0 commit comments

Comments
 (0)