Skip to content

Commit 3d91f47

Browse files
committed
Add hydrate option to createRoot
1 parent 1bb13d5 commit 3d91f47

13 files changed

+83
-34
lines changed

src/renderers/dom/fiber/ReactDOMFiberEntry.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ function renderSubtreeIntoContainer(
732732
);
733733
}
734734
}
735-
const newRoot = DOMRenderer.createContainer(container);
735+
const newRoot = DOMRenderer.createContainer(container, shouldHydrate);
736736
root = container._reactRootContainer = newRoot;
737737
// Initial mount should not be batched.
738738
DOMRenderer.unbatchedUpdates(() => {
@@ -769,7 +769,7 @@ type RootOptions = {
769769
};
770770

771771
function ReactRoot(container: Container, hydrate: boolean) {
772-
const root = DOMRenderer.createContainer(container);
772+
const root = DOMRenderer.createContainer(container, hydrate);
773773
this._reactRootContainer = root;
774774
}
775775
ReactRoot.prototype.render = function(

src/renderers/dom/shared/__tests__/ReactDOMRoot-test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
var React = require('react');
1313
var ReactDOM = require('react-dom');
14+
var ReactDOMServer = require('react-dom/server');
1415

1516
describe('ReactDOMRoot', () => {
1617
let container;
@@ -32,4 +33,39 @@ describe('ReactDOMRoot', () => {
3233
root.unmount();
3334
expect(container.textContent).toEqual('');
3435
});
36+
37+
it('supports hydration', async () => {
38+
const markup = await new Promise(resolve =>
39+
resolve(
40+
ReactDOMServer.renderToString(<div><span className="extra" /></div>),
41+
),
42+
);
43+
44+
spyOn(console, 'error');
45+
46+
// Does not hydrate by default
47+
const container1 = document.createElement('div');
48+
container1.innerHTML = markup;
49+
const root1 = ReactDOM.createRoot(container1);
50+
root1.render(<div><span /></div>);
51+
expect(console.error.calls.count()).toBe(0);
52+
53+
// Accepts `hydrate` option
54+
const container2 = document.createElement('div');
55+
container2.innerHTML = markup;
56+
const root2 = ReactDOM.createRoot(container2, {hydrate: true});
57+
root2.render(<div><span /></div>);
58+
expect(console.error.calls.count()).toBe(1);
59+
expect(console.error.calls.argsFor(0)[0]).toMatch('Extra attributes');
60+
});
61+
62+
it('does not clear existing children', async () => {
63+
spyOn(console, 'error');
64+
container.innerHTML = '<div>a</div><div>b</div>';
65+
const root = ReactDOM.createRoot(container);
66+
root.render(<div><span>c</span><span>d</span></div>);
67+
expect(container.textContent).toEqual('abcd');
68+
root.render(<div><span>d</span><span>c</span></div>);
69+
expect(container.textContent).toEqual('abdc');
70+
});
3571
});

src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2638,6 +2638,12 @@ describe('ReactDOMServerIntegration', () => {
26382638
it('should error reconnecting different element types', () =>
26392639
expectMarkupMismatch(<div />, <span />));
26402640

2641+
it('should error reconnecting fewer root children', () =>
2642+
expectMarkupMismatch(<span key="a" />, [
2643+
<span key="a" />,
2644+
<span key="b" />,
2645+
]));
2646+
26412647
it('should error reconnecting missing attributes', () =>
26422648
expectMarkupMismatch(<div id="foo" />, <div />));
26432649

src/renderers/dom/shared/__tests__/ReactRenderDocument-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ describe('rendering React components at document', () => {
396396
expect(container.textContent).toBe('parsnip');
397397
expectDev(console.error.calls.count()).toBe(1);
398398
expectDev(console.error.calls.argsFor(0)[0]).toContain(
399-
'Did not expect server HTML to contain the text node "potato" in <div>.',
399+
'Expected server HTML to contain a matching <div> in <div>.',
400400
);
401401
});
402402

src/renderers/native-cs/ReactNativeCSFiberEntry.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ const ReactNativeCSFiber: ReactNativeCSType = {
194194
if (!root) {
195195
// TODO (bvaughn): If we decide to keep the wrapper component,
196196
// We could create a wrapper for containerTag as well to reduce special casing.
197-
root = ReactNativeCSFiberRenderer.createContainer(containerTag);
197+
root = ReactNativeCSFiberRenderer.createContainer(containerTag, false);
198198
roots.set(containerTag, root);
199199
}
200200
ReactNativeCSFiberRenderer.updateContainer(element, root, null, callback);

src/renderers/native-rt/ReactNativeRTFiberEntry.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const ReactNativeRTFiber: ReactNativeRTType = {
5353
if (!root) {
5454
// TODO (bvaughn): If we decide to keep the wrapper component,
5555
// We could create a wrapper for containerTag as well to reduce special casing.
56-
root = ReactNativeRTFiberRenderer.createContainer(containerTag);
56+
root = ReactNativeRTFiberRenderer.createContainer(containerTag, false);
5757
roots.set(containerTag, root);
5858
}
5959
ReactNativeRTFiberRenderer.updateContainer(element, root, null, callback);

src/renderers/native/ReactNativeFiberEntry.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const ReactNativeFiber: ReactNativeType = {
5252
if (!root) {
5353
// TODO (bvaughn): If we decide to keep the wrapper component,
5454
// We could create a wrapper for containerTag as well to reduce special casing.
55-
root = ReactNativeFiberRenderer.createContainer(containerTag);
55+
root = ReactNativeFiberRenderer.createContainer(containerTag, false);
5656
roots.set(containerTag, root);
5757
}
5858
ReactNativeFiberRenderer.updateContainer(element, root, null, callback);

src/renderers/noop/ReactNoopEntry.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ var ReactNoop = {
269269
if (!root) {
270270
const container = {rootID: rootID, children: []};
271271
rootContainers.set(rootID, container);
272-
root = NoopRenderer.createContainer(container);
272+
root = NoopRenderer.createContainer(container, false);
273273
roots.set(rootID, root);
274274
}
275275
NoopRenderer.updateContainer(element, root, null, callback);

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,8 +338,10 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
338338
return bailoutOnAlreadyFinishedWork(current, workInProgress);
339339
}
340340
const element = state.element;
341+
const root: FiberRoot = workInProgress.stateNode;
341342
if (
342343
(current === null || current.child === null) &&
344+
root.hydrate &&
343345
enterHydrationState(workInProgress)
344346
) {
345347
// If we don't have any current children this might be the first pass.

src/renderers/shared/fiber/ReactFiberHydrationContext.js

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
7777
didNotMatchHydratedTextInstance,
7878
didNotHydrateContainerInstance,
7979
didNotHydrateInstance,
80-
// TODO: These are currently unused, see below.
81-
// didNotFindHydratableContainerInstance,
82-
// didNotFindHydratableContainerTextInstance,
80+
didNotFindHydratableContainerInstance,
81+
didNotFindHydratableContainerTextInstance,
8382
didNotFindHydratableInstance,
8483
didNotFindHydratableTextInstance,
8584
} = hydration;
@@ -140,25 +139,25 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
140139
fiber.effectTag |= Placement;
141140
if (__DEV__) {
142141
switch (returnFiber.tag) {
143-
// TODO: Currently we don't warn for insertions into the root because
144-
// we always insert into the root in the non-hydrating case. We just
145-
// delete the existing content. Reenable this once we have a better
146-
// strategy for determining if we're hydrating or not.
147-
// case HostRoot: {
148-
// const parentContainer = returnFiber.stateNode.containerInfo;
149-
// switch (fiber.tag) {
150-
// case HostComponent:
151-
// const type = fiber.type;
152-
// const props = fiber.pendingProps;
153-
// didNotFindHydratableContainerInstance(parentContainer, type, props);
154-
// break;
155-
// case HostText:
156-
// const text = fiber.pendingProps;
157-
// didNotFindHydratableContainerTextInstance(parentContainer, text);
158-
// break;
159-
// }
160-
// break;
161-
// }
142+
case HostRoot: {
143+
const parentContainer = returnFiber.stateNode.containerInfo;
144+
switch (fiber.tag) {
145+
case HostComponent:
146+
const type = fiber.type;
147+
const props = fiber.pendingProps;
148+
didNotFindHydratableContainerInstance(
149+
parentContainer,
150+
type,
151+
props,
152+
);
153+
break;
154+
case HostText:
155+
const text = fiber.pendingProps;
156+
didNotFindHydratableContainerTextInstance(parentContainer, text);
157+
break;
158+
}
159+
break;
160+
}
162161
case HostComponent: {
163162
const parentType = returnFiber.type;
164163
const parentProps = returnFiber.memoizedProps;

0 commit comments

Comments
 (0)