diff --git a/src/renderers/dom/fiber/ReactDOMFiberEntry.js b/src/renderers/dom/fiber/ReactDOMFiberEntry.js
index bb92698619..3a79c1f50c 100644
--- a/src/renderers/dom/fiber/ReactDOMFiberEntry.js
+++ b/src/renderers/dom/fiber/ReactDOMFiberEntry.js
@@ -118,7 +118,7 @@ function shouldReuseContent(container) {
const rootElement = getReactRootElementInContainer(container);
return !!(rootElement &&
rootElement.nodeType === ELEMENT_NODE &&
- rootElement.getAttribute(ID_ATTRIBUTE_NAME));
+ rootElement.hasAttribute(ID_ATTRIBUTE_NAME));
}
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
@@ -392,7 +392,14 @@ var DOMRenderer = ReactFiberReconciler({
return instance.nodeType === 1 && type === instance.nodeName.toLowerCase();
},
- canHydrateTextInstance(instance: Instance | TextInstance): boolean {
+ canHydrateTextInstance(
+ instance: Instance | TextInstance,
+ text: string,
+ ): boolean {
+ if (text === '') {
+ // Empty strings are not parsed by HTML so there won't be a correct match here.
+ return false;
+ }
return instance.nodeType === 3;
},
@@ -401,11 +408,9 @@ var DOMRenderer = ReactFiberReconciler({
): null | Instance | TextInstance {
let node = instance.nextSibling;
// Skip non-hydratable nodes.
- /*
while (node && node.nodeType !== 1 && node.nodeType !== 3) {
node = node.nextSibling;
}
- */
return (node: any);
},
@@ -414,11 +419,9 @@ var DOMRenderer = ReactFiberReconciler({
): null | Instance | TextInstance {
let next = parentInstance.firstChild;
// Skip non-hydratable nodes.
- /*
while (next && next.nodeType !== 1 && next.nodeType !== 3) {
next = next.nextSibling;
}
- */
return (next: any);
},
diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
index 64947e423e..849f8ec6e5 100644
--- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
@@ -606,8 +606,9 @@ describe('ReactDOMServerIntegration', () => {
// with Fiber, there are just three separate text node children,
// each of which is blank.
if (render === serverRender || render === streamRender) {
- // For plain server markup result we expect six comment nodes.
- expect(e.childNodes.length).toBe(6);
+ // For plain server markup result we should have no text nodes if
+ // they're all empty.
+ expect(e.childNodes.length).toBe(0);
expect(e.textContent).toBe('');
} else {
expect(e.childNodes.length).toBe(3);
@@ -627,12 +628,18 @@ describe('ReactDOMServerIntegration', () => {
itRenders('a div with multiple whitespace children', async render => {
const e = await render(
{' '}{' '}{' '}
);
if (ReactDOMFeatureFlags.useFiber) {
- // with Fiber, there are just three text nodes.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(9);
- expectTextNode(e.childNodes[1], ' ');
+ // with Fiber, there are normally just three text nodes
+ if (
+ render === serverRender ||
+ render === clientRenderOnServerString ||
+ render === streamRender
+ ) {
+ // For plain server markup result we have comments between.
+ // If we're able to hydrate, they remain.
+ expect(e.childNodes.length).toBe(5);
+ expectTextNode(e.childNodes[0], ' ');
+ expectTextNode(e.childNodes[2], ' ');
expectTextNode(e.childNodes[4], ' ');
- expectTextNode(e.childNodes[7], ' ');
} else {
expect(e.childNodes.length).toBe(3);
expectTextNode(e.childNodes[0], ' ');
@@ -652,10 +659,7 @@ describe('ReactDOMServerIntegration', () => {
itRenders('a div with text sibling to a node', async render => {
const e = await render(TextMore Text
);
let spanNode;
- if (
- ReactDOMFeatureFlags.useFiber &&
- (render !== serverRender && render !== streamRender)
- ) {
+ if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there are only two children, the "Text" text node and
// the span element.
expect(e.childNodes.length).toBe(2);
@@ -666,14 +670,7 @@ describe('ReactDOMServerIntegration', () => {
expect(e.childNodes.length).toBe(4);
spanNode = e.childNodes[3];
}
- if (
- ReactDOMFeatureFlags.useFiber &&
- (render === serverRender || render === streamRender)
- ) {
- expectTextNode(e.childNodes[1], 'Text');
- } else {
- expectTextNode(e.childNodes[0], 'Text');
- }
+ expectTextNode(e.childNodes[0], 'Text');
expect(spanNode.tagName).toBe('SPAN');
expect(spanNode.childNodes.length).toBe(1);
expectNode(spanNode.firstChild, TEXT_NODE_TYPE, 'More Text');
@@ -698,8 +695,8 @@ describe('ReactDOMServerIntegration', () => {
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there are just two text nodes.
if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(5);
- expectTextNode(e.childNodes[3], 'foo');
+ expect(e.childNodes.length).toBe(1);
+ expectTextNode(e.childNodes[0], 'foo');
} else {
expect(e.childNodes.length).toBe(2);
expectTextNode(e.childNodes[0], '');
@@ -720,8 +717,8 @@ describe('ReactDOMServerIntegration', () => {
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there are just two text nodes.
if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(5);
- expectTextNode(e.childNodes[1], 'foo');
+ expect(e.childNodes.length).toBe(1);
+ expectTextNode(e.childNodes[0], 'foo');
} else {
expect(e.childNodes.length).toBe(2);
expectTextNode(e.childNodes[0], 'foo');
@@ -741,10 +738,15 @@ describe('ReactDOMServerIntegration', () => {
const e = await render({'foo'}{'bar'}
);
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there are just two text nodes.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(6);
- expectTextNode(e.childNodes[1], 'foo');
- expectTextNode(e.childNodes[4], 'bar');
+ if (
+ render === serverRender ||
+ render === clientRenderOnServerString ||
+ render === streamRender
+ ) {
+ // In the server render output there's a comment between them.
+ expect(e.childNodes.length).toBe(3);
+ expectTextNode(e.childNodes[0], 'foo');
+ expectTextNode(e.childNodes[2], 'bar');
} else {
expect(e.childNodes.length).toBe(2);
expectTextNode(e.childNodes[0], 'foo');
@@ -776,10 +778,15 @@ describe('ReactDOMServerIntegration', () => {
const e = await render({'foo'}{40}
);
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there are just two text nodes.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(6);
- expectTextNode(e.childNodes[1], 'foo');
- expectTextNode(e.childNodes[4], '40');
+ if (
+ render === serverRender ||
+ render === clientRenderOnServerString ||
+ render === streamRender
+ ) {
+ // In the server markup there's a comment between.
+ expect(e.childNodes.length).toBe(3);
+ expectTextNode(e.childNodes[0], 'foo');
+ expectTextNode(e.childNodes[2], '40');
} else {
expect(e.childNodes.length).toBe(2);
expectTextNode(e.childNodes[0], 'foo');
@@ -816,12 +823,7 @@ describe('ReactDOMServerIntegration', () => {
const e = await render(
);
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, an empty component results in no markup.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(1);
- expectEmptyNode(e.firstChild);
- } else {
- expect(e.childNodes.length).toBe(0);
- }
+ expect(e.childNodes.length).toBe(0);
} else {
// with Stack, an empty component results in one react-empty comment
// node.
@@ -834,13 +836,8 @@ describe('ReactDOMServerIntegration', () => {
const e = await render({null}foo
);
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there is just one text node.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(3);
- expectTextNode(e.childNodes[1], 'foo');
- } else {
- expect(e.childNodes.length).toBe(1);
- expectTextNode(e.childNodes[0], 'foo');
- }
+ expect(e.childNodes.length).toBe(1);
+ expectTextNode(e.childNodes[0], 'foo');
} else {
// with Stack, there's a text node surronded by react-text comment nodes.
expect(e.childNodes.length).toBe(3);
@@ -852,13 +849,8 @@ describe('ReactDOMServerIntegration', () => {
const e = await render({false}foo
);
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there is just one text node.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(3);
- expectTextNode(e.childNodes[1], 'foo');
- } else {
- expect(e.childNodes.length).toBe(1);
- expectTextNode(e.childNodes[0], 'foo');
- }
+ expect(e.childNodes.length).toBe(1);
+ expectTextNode(e.childNodes[0], 'foo');
} else {
// with Stack, there's a text node surronded by react-text comment nodes.
expect(e.childNodes.length).toBe(3);
@@ -870,13 +862,8 @@ describe('ReactDOMServerIntegration', () => {
const e = await render({false}{null}foo{null}{false}
);
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there is just one text node.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(3);
- expectTextNode(e.childNodes[1], 'foo');
- } else {
- expect(e.childNodes.length).toBe(1);
- expectTextNode(e.childNodes[0], 'foo');
- }
+ expect(e.childNodes.length).toBe(1);
+ expectTextNode(e.childNodes[0], 'foo');
} else {
// with Stack, there's a text node surronded by react-text comment nodes.
expect(e.childNodes.length).toBe(3);
@@ -1075,17 +1062,10 @@ describe('ReactDOMServerIntegration', () => {
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there are three children: the child1 element, a
// single space text node, and the child2 element.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(5);
- child1 = e.childNodes[0];
- textNode = e.childNodes[2];
- child2 = e.childNodes[4];
- } else {
- expect(e.childNodes.length).toBe(3);
- child1 = e.childNodes[0];
- textNode = e.childNodes[1];
- child2 = e.childNodes[2];
- }
+ expect(e.childNodes.length).toBe(3);
+ child1 = e.childNodes[0];
+ textNode = e.childNodes[1];
+ child2 = e.childNodes[2];
} else {
// with Stack, there are five children: the child1 element, a single
// space surrounded by react-text comments, and the child2 element.
@@ -1111,17 +1091,10 @@ describe('ReactDOMServerIntegration', () => {
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there are three children: a one-space text node, the
// child element, and a two-space text node.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(7);
- textNode1 = e.childNodes[1];
- child = e.childNodes[3];
- textNode2 = e.childNodes[5];
- } else {
- expect(e.childNodes.length).toBe(3);
- textNode1 = e.childNodes[0];
- child = e.childNodes[1];
- textNode2 = e.childNodes[2];
- }
+ expect(e.childNodes.length).toBe(3);
+ textNode1 = e.childNodes[0];
+ child = e.childNodes[1];
+ textNode2 = e.childNodes[2];
} else {
// with Stack, there are 7 children: a one-space text node surrounded
// by react-text comments, the child element, and a two-space text node
@@ -1153,10 +1126,14 @@ describe('ReactDOMServerIntegration', () => {
);
if (ReactDOMFeatureFlags.useFiber) {
// with Fiber, there are just two text nodes.
- if (render === serverRender || render === streamRender) {
- expect(e.childNodes.length).toBe(6);
- expectTextNode(e.childNodes[1], 'Text1"');
- expectTextNode(e.childNodes[4], 'Text2"');
+ if (
+ render === serverRender ||
+ render === clientRenderOnServerString ||
+ render === streamRender
+ ) {
+ expect(e.childNodes.length).toBe(3);
+ expectTextNode(e.childNodes[0], 'Text1"');
+ expectTextNode(e.childNodes[2], 'Text2"');
} else {
expect(e.childNodes.length).toBe(2);
expectTextNode(e.childNodes[0], 'Text1"');
diff --git a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js
index 79cca198f0..53d81dac08 100644
--- a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js
@@ -53,7 +53,7 @@ describe('ReactDOMServer', () => {
ROOT_ATTRIBUTE_NAME +
'="" ' +
ID_ATTRIBUTE_NAME +
- '="[^"]+" ' +
+ '="[^"]*" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME +
'="[^"]+">hello world',
),
@@ -68,7 +68,7 @@ describe('ReactDOMServer', () => {
ROOT_ATTRIBUTE_NAME +
'="" ' +
ID_ATTRIBUTE_NAME +
- '="[^"]+" ' +
+ '="[^"]*" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME +
'="[^"]+"/>',
),
@@ -83,7 +83,7 @@ describe('ReactDOMServer', () => {
ROOT_ATTRIBUTE_NAME +
'="" ' +
ID_ATTRIBUTE_NAME +
- '="[^"]+" ' +
+ '="[^"]*" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME +
'="[^"]+"/>',
),
@@ -98,7 +98,11 @@ describe('ReactDOMServer', () => {
}
var response = ReactDOMServer.renderToString();
- expect(response).toBe('');
+ if (ReactDOMFeatureFlags.useFiber) {
+ expect(response).toBe('');
+ } else {
+ expect(response).toBe('');
+ }
});
// TODO: Test that listeners are not registered onto any document/container.
@@ -123,14 +127,16 @@ describe('ReactDOMServer', () => {
ROOT_ATTRIBUTE_NAME +
'="" ' +
ID_ATTRIBUTE_NAME +
- '="[^"]+" ' +
+ '="[^"]*" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME +
'="[^"]+">' +
'' +
- 'My name is ' +
- 'child' +
+ '="[^"]*">' +
+ (ReactDOMFeatureFlags.useFiber
+ ? 'My name is child'
+ : 'My name is ' +
+ 'child') +
'' +
'',
),
@@ -190,11 +196,13 @@ describe('ReactDOMServer', () => {
ROOT_ATTRIBUTE_NAME +
'="" ' +
ID_ATTRIBUTE_NAME +
- '="[^"]+" ' +
+ '="[^"]*" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME +
'="[^"]+">' +
- 'Component name: ' +
- 'TestComponent' +
+ (ReactDOMFeatureFlags.useFiber
+ ? 'Component name: TestComponent'
+ : 'Component name: ' +
+ 'TestComponent') +
'',
),
);
diff --git a/src/renderers/shared/fiber/ReactFiberHydrationContext.js b/src/renderers/shared/fiber/ReactFiberHydrationContext.js
index 920934fb6b..ab0b98fcb2 100644
--- a/src/renderers/shared/fiber/ReactFiberHydrationContext.js
+++ b/src/renderers/shared/fiber/ReactFiberHydrationContext.js
@@ -108,11 +108,12 @@ module.exports = function(
switch (fiber.tag) {
case HostComponent: {
const type = fiber.type;
- const props = fiber.memoizedProps;
+ const props = fiber.pendingProps;
return canHydrateInstance(nextInstance, type, props);
}
case HostText: {
- return canHydrateTextInstance(nextInstance);
+ const text = fiber.pendingProps;
+ return canHydrateTextInstance(nextInstance, text);
}
default:
return false;
diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js
index 5358985348..71b5ff2fbb 100644
--- a/src/renderers/shared/fiber/ReactFiberReconciler.js
+++ b/src/renderers/shared/fiber/ReactFiberReconciler.js
@@ -123,7 +123,7 @@ export type HostConfig = {
// Optional hydration
canHydrateInstance?: (instance: I | TI, type: T, props: P) => boolean,
- canHydrateTextInstance?: (instance: I | TI) => boolean,
+ canHydrateTextInstance?: (instance: I | TI, text: string) => boolean,
getNextHydratableSibling?: (instance: I | TI) => null | I | TI,
getFirstHydratableChild?: (parentInstance: I | C) => null | I | TI,
hydrateInstance?: (
diff --git a/src/renderers/shared/server/ReactPartialRenderer.js b/src/renderers/shared/server/ReactPartialRenderer.js
index 53ba1d9a37..2061ce4b66 100644
--- a/src/renderers/shared/server/ReactPartialRenderer.js
+++ b/src/renderers/shared/server/ReactPartialRenderer.js
@@ -181,7 +181,6 @@ function createOpenTagMarkup(
props,
makeStaticMarkup,
isRootElement,
- domID,
instForDebug,
) {
var ret = '<' + tagVerbatim;
@@ -230,7 +229,7 @@ function createOpenTagMarkup(
if (isRootElement) {
ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
}
- ret += ' ' + DOMPropertyOperations.createMarkupForID(domID);
+ ret += ' ' + DOMPropertyOperations.createMarkupForID('');
return ret;
}
@@ -346,9 +345,9 @@ class ReactDOMServerRenderer {
footer: '',
},
];
- this.idCounter = 1;
this.exhausted = false;
this.currentSelectValue = null;
+ this.previousWasTextNode = false;
this.makeStaticMarkup = makeStaticMarkup;
}
@@ -366,6 +365,7 @@ class ReactDOMServerRenderer {
var frame = this.stack[this.stack.length - 1];
if (frame.childIndex >= frame.children.length) {
out += frame.footer;
+ this.previousWasTextNode = false;
this.stack.pop();
if (frame.tag === 'select') {
this.currentSelectValue = null;
@@ -380,26 +380,22 @@ class ReactDOMServerRenderer {
render(child, context) {
if (typeof child === 'string' || typeof child === 'number') {
- if (this.makeStaticMarkup) {
- return escapeTextContentForBrowser('' + child);
+ var text = '' + child;
+ if (text === '') {
+ return '';
}
- return (
- '' +
- escapeTextContentForBrowser('' + child) +
- ''
- );
+ if (this.makeStaticMarkup) {
+ return escapeTextContentForBrowser(text);
+ }
+ if (this.previousWasTextNode) {
+ return '' + escapeTextContentForBrowser(text);
+ }
+ this.previousWasTextNode = true;
+ return escapeTextContentForBrowser(text);
} else {
({child, context} = resolve(child, context));
if (child === null || child === false) {
- if (this.makeStaticMarkup) {
- // Normally we'd insert a comment node, but since this is a situation
- // where React won't take over (static pages), we can simply return
- // nothing.
- return '';
- }
- return '';
+ return '';
} else {
return this.renderDOM(child, context);
}
@@ -645,7 +641,6 @@ class ReactDOMServerRenderer {
props,
this.makeStaticMarkup,
this.stack.length === 1,
- this.idCounter++,
null,
);
var footer = '';