Skip to content

Commit

Permalink
Add hydrate option to createRoot
Browse files Browse the repository at this point in the history
  • Loading branch information
acdlite committed Oct 14, 2017
1 parent 1bb13d5 commit eef374b
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/renderers/dom/fiber/ReactDOMFiberEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,7 @@ function renderSubtreeIntoContainer(
);
}
}
const newRoot = DOMRenderer.createContainer(container);
const newRoot = DOMRenderer.createContainer(container, shouldHydrate);
root = container._reactRootContainer = newRoot;
// Initial mount should not be batched.
DOMRenderer.unbatchedUpdates(() => {
Expand Down Expand Up @@ -769,7 +769,7 @@ type RootOptions = {
};

function ReactRoot(container: Container, hydrate: boolean) {
const root = DOMRenderer.createContainer(container);
const root = DOMRenderer.createContainer(container, hydrate);
this._reactRootContainer = root;
}
ReactRoot.prototype.render = function(
Expand Down
36 changes: 36 additions & 0 deletions src/renderers/dom/shared/__tests__/ReactDOMRoot-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

var React = require('react');
var ReactDOM = require('react-dom');
var ReactDOMServer = require('react-dom/server');

describe('ReactDOMRoot', () => {
let container;
Expand All @@ -32,4 +33,39 @@ describe('ReactDOMRoot', () => {
root.unmount();
expect(container.textContent).toEqual('');
});

it('supports hydration', async () => {
const markup = await new Promise(resolve =>
resolve(
ReactDOMServer.renderToString(<div><span className="extra" /></div>),
),
);

spyOn(console, 'error');

// Does not hydrate by default
const container1 = document.createElement('div');
container1.innerHTML = markup;
const root1 = ReactDOM.createRoot(container1);
root1.render(<div><span /></div>);
expect(console.error.calls.count()).toBe(0);

// Accepts `hydrate` option
const container2 = document.createElement('div');
container2.innerHTML = markup;
const root2 = ReactDOM.createRoot(container2, {hydrate: true});
root2.render(<div><span /></div>);
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toMatch('Extra attributes');
});

it('does not clear existing children', async () => {
spyOn(console, 'error');
container.innerHTML = '<div>a</div><div>b</div>';
const root = ReactDOM.createRoot(container);
root.render(<div><span>c</span><span>d</span></div>);
expect(container.textContent).toEqual('abcd');
root.render(<div><span>d</span><span>c</span></div>);
expect(container.textContent).toEqual('abdc');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2638,6 +2638,12 @@ describe('ReactDOMServerIntegration', () => {
it('should error reconnecting different element types', () =>
expectMarkupMismatch(<div />, <span />));

it('should error reconnecting fewer root children', () =>
expectMarkupMismatch(<span key="a" />, [
<span key="a" />,
<span key="b" />,
]));

it('should error reconnecting missing attributes', () =>
expectMarkupMismatch(<div id="foo" />, <div />));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ describe('rendering React components at document', () => {
expect(container.textContent).toBe('parsnip');
expectDev(console.error.calls.count()).toBe(1);
expectDev(console.error.calls.argsFor(0)[0]).toContain(
'Did not expect server HTML to contain the text node "potato" in <div>.',
'Expected server HTML to contain a matching <div> in <div>.',
);
});

Expand Down
2 changes: 2 additions & 0 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,10 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
const element = state.element;
const root: FiberRoot = workInProgress.stateNode;
if (
(current === null || current.child === null) &&
root.hydrate &&
enterHydrationState(workInProgress)
) {
// If we don't have any current children this might be the first pass.
Expand Down
43 changes: 21 additions & 22 deletions src/renderers/shared/fiber/ReactFiberHydrationContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
didNotMatchHydratedTextInstance,
didNotHydrateContainerInstance,
didNotHydrateInstance,
// TODO: These are currently unused, see below.
// didNotFindHydratableContainerInstance,
// didNotFindHydratableContainerTextInstance,
didNotFindHydratableContainerInstance,
didNotFindHydratableContainerTextInstance,
didNotFindHydratableInstance,
didNotFindHydratableTextInstance,
} = hydration;
Expand Down Expand Up @@ -140,25 +139,25 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
fiber.effectTag |= Placement;
if (__DEV__) {
switch (returnFiber.tag) {
// TODO: Currently we don't warn for insertions into the root because
// we always insert into the root in the non-hydrating case. We just
// delete the existing content. Reenable this once we have a better
// strategy for determining if we're hydrating or not.
// case HostRoot: {
// const parentContainer = returnFiber.stateNode.containerInfo;
// switch (fiber.tag) {
// case HostComponent:
// const type = fiber.type;
// const props = fiber.pendingProps;
// didNotFindHydratableContainerInstance(parentContainer, type, props);
// break;
// case HostText:
// const text = fiber.pendingProps;
// didNotFindHydratableContainerTextInstance(parentContainer, text);
// break;
// }
// break;
// }
case HostRoot: {
const parentContainer = returnFiber.stateNode.containerInfo;
switch (fiber.tag) {
case HostComponent:
const type = fiber.type;
const props = fiber.pendingProps;
didNotFindHydratableContainerInstance(
parentContainer,
type,
props,
);
break;
case HostText:
const text = fiber.pendingProps;
didNotFindHydratableContainerTextInstance(parentContainer, text);
break;
}
break;
}
case HostComponent: {
const parentType = returnFiber.type;
const parentProps = returnFiber.memoizedProps;
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
}

return {
createContainer(containerInfo: C): OpaqueRoot {
return createFiberRoot(containerInfo);
createContainer(containerInfo: C, hydrate: boolean): OpaqueRoot {
return createFiberRoot(containerInfo, hydrate);
},

updateContainer(
Expand Down
8 changes: 7 additions & 1 deletion src/renderers/shared/fiber/ReactFiberRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,14 @@ export type FiberRoot = {
// Top context object, used by renderSubtreeIntoContainer
context: Object | null,
pendingContext: Object | null,
// Determines if we should attempt to hydrate on the initial mount
+hydrate: boolean,
};

exports.createFiberRoot = function(containerInfo: any): FiberRoot {
exports.createFiberRoot = function(
containerInfo: any,
hydrate: boolean,
): FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber();
Expand All @@ -39,6 +44,7 @@ exports.createFiberRoot = function(containerInfo: any): FiberRoot {
nextScheduledRoot: null,
context: null,
pendingContext: null,
hydrate,
};
uninitializedFiber.stateNode = root;
return root;
Expand Down

0 comments on commit eef374b

Please sign in to comment.