diff --git a/packages/react-dom/index.classic.fb.js b/packages/react-dom/index.classic.fb.js
index 234aa33066..5f4f04e634 100644
--- a/packages/react-dom/index.classic.fb.js
+++ b/packages/react-dom/index.classic.fb.js
@@ -23,6 +23,7 @@ export {
createPortal,
createRoot,
createRoot as unstable_createRoot, // TODO Remove once callsites use createRoot
+ hydrateRoot,
findDOMNode,
flushSync,
hydrate,
diff --git a/packages/react-dom/index.experimental.js b/packages/react-dom/index.experimental.js
index 5a1c9f191f..5cb691740e 100644
--- a/packages/react-dom/index.experimental.js
+++ b/packages/react-dom/index.experimental.js
@@ -11,6 +11,7 @@ export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
createPortal,
createRoot,
+ hydrateRoot,
findDOMNode,
flushSync,
hydrate,
diff --git a/packages/react-dom/index.js b/packages/react-dom/index.js
index 6a824b2f70..7b89695091 100644
--- a/packages/react-dom/index.js
+++ b/packages/react-dom/index.js
@@ -14,6 +14,7 @@ export {
createPortal,
createRoot,
createRoot as unstable_createRoot,
+ hydrateRoot,
findDOMNode,
flushSync,
hydrate,
diff --git a/packages/react-dom/index.modern.fb.js b/packages/react-dom/index.modern.fb.js
index b53a8cf9ad..b12c249286 100644
--- a/packages/react-dom/index.modern.fb.js
+++ b/packages/react-dom/index.modern.fb.js
@@ -12,6 +12,7 @@ export {
createPortal,
createRoot,
createRoot as unstable_createRoot, // TODO Remove once callsites use createRoot
+ hydrateRoot,
flushSync,
unstable_batchedUpdates,
unstable_createEventHandle,
diff --git a/packages/react-dom/index.stable.js b/packages/react-dom/index.stable.js
index 55d515a9fc..7d8b01be53 100644
--- a/packages/react-dom/index.stable.js
+++ b/packages/react-dom/index.stable.js
@@ -11,6 +11,7 @@ export {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
createPortal,
createRoot,
+ hydrateRoot,
findDOMNode,
flushSync,
hydrate,
diff --git a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
index 9fe03cc2a9..6fb631fb4d 100644
--- a/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMRoot-test.js
@@ -96,11 +96,10 @@ describe('ReactDOMRoot', () => {
);
Scheduler.unstable_flushAll();
- // Accepts `hydrate` option
const container2 = document.createElement('div');
container2.innerHTML = markup;
- const root2 = ReactDOM.createRoot(container2, {hydrate: true});
- root2.render(
+ ReactDOM.hydrateRoot(
+ container2,
,
@@ -191,7 +190,7 @@ describe('ReactDOMRoot', () => {
// We care about this warning:
'You are calling ReactDOM.hydrate() on a container that was previously ' +
'passed to ReactDOM.createRoot(). This is not supported. ' +
- 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
+ 'Did you mean to call hydrateRoot(container, element)?',
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
'Replacing React-rendered children with a new root component.',
],
diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js
index 848e889a35..7355e0be45 100644
--- a/packages/react-dom/src/client/ReactDOM.js
+++ b/packages/react-dom/src/client/ReactDOM.js
@@ -17,7 +17,7 @@ import {
unstable_renderSubtreeIntoContainer,
unmountComponentAtNode,
} from './ReactDOMLegacy';
-import {createRoot, isValidContainer} from './ReactDOMRoot';
+import {createRoot, hydrateRoot, isValidContainer} from './ReactDOMRoot';
import {createEventHandle} from './ReactDOMEventHandle';
import {
@@ -182,6 +182,7 @@ export {
unmountComponentAtNode,
// exposeConcurrentModeAPIs
createRoot,
+ hydrateRoot,
flushControlled as unstable_flushControlled,
scheduleHydration as unstable_scheduleHydration,
// Disabled behind disableUnstableRenderSubtreeIntoContainer
diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js
index 908719d679..bad70b6ba9 100644
--- a/packages/react-dom/src/client/ReactDOMHostConfig.js
+++ b/packages/react-dom/src/client/ReactDOMHostConfig.js
@@ -14,7 +14,6 @@ import type {
IntersectionObserverOptions,
ObserveVisibleRectsCallback,
} from 'react-reconciler/src/ReactTestSelectors';
-import type {RootType} from './ReactDOMRoot';
import type {ReactScopeInstance} from 'shared/ReactTypes';
import {
@@ -105,8 +104,8 @@ export type EventTargetChildElement = {
...
};
export type Container =
- | (Element & {_reactRootContainer?: RootType, ...})
- | (Document & {_reactRootContainer?: RootType, ...});
+ | (Element & {_reactRootContainer?: FiberRoot, ...})
+ | (Document & {_reactRootContainer?: FiberRoot, ...});
export type Instance = Element;
export type TextInstance = Text;
export type SuspenseInstance = Comment & {_reactRetry?: () => void, ...};
diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js
index e85a5541b1..a62a7fb444 100644
--- a/packages/react-dom/src/client/ReactDOMLegacy.js
+++ b/packages/react-dom/src/client/ReactDOMLegacy.js
@@ -8,16 +8,17 @@
*/
import type {Container} from './ReactDOMHostConfig';
-import type {RootType} from './ReactDOMRoot';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {ReactNodeList} from 'shared/ReactTypes';
import {
getInstanceFromNode,
isContainerMarkedAsRoot,
+ markContainerAsRoot,
unmarkContainerAsRoot,
} from './ReactDOMComponentTree';
-import {createLegacyRoot, isValidContainer} from './ReactDOMRoot';
+import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
+import {isValidContainerLegacy} from './ReactDOMRoot';
import {
DOCUMENT_NODE,
ELEMENT_NODE,
@@ -25,6 +26,7 @@ import {
} from '../shared/HTMLNodeType';
import {
+ createContainer,
findHostInstanceWithNoPortals,
updateContainer,
unbatchedUpdates,
@@ -32,6 +34,7 @@ import {
findHostInstance,
findHostInstanceWithWarning,
} from 'react-reconciler/src/ReactFiberReconciler';
+import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import invariant from 'shared/invariant';
import ReactSharedInternals from 'shared/ReactSharedInternals';
@@ -45,7 +48,7 @@ if (__DEV__) {
topLevelUpdateWarnings = (container: Container) => {
if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
const hostInstance = findHostInstanceWithNoPortals(
- container._reactRootContainer._internalRoot.current,
+ container._reactRootContainer.current,
);
if (hostInstance) {
if (hostInstance.parentNode !== container) {
@@ -103,7 +106,7 @@ function getReactRootElementInContainer(container: any) {
function legacyCreateRootFromDOMContainer(
container: Container,
forceHydrate: boolean,
-): RootType {
+): FiberRoot {
// First clear any existing content.
if (!forceHydrate) {
let rootSibling;
@@ -112,14 +115,21 @@ function legacyCreateRootFromDOMContainer(
}
}
- return createLegacyRoot(
+ const root = createContainer(
container,
- forceHydrate
- ? {
- hydrate: true,
- }
- : undefined,
+ LegacyRoot,
+ forceHydrate,
+ null, // hydrationCallbacks
+ false, // isStrictMode
+ false, // concurrentUpdatesByDefaultOverride,
);
+ markContainerAsRoot(root.current, container);
+
+ const rootContainerElement =
+ container.nodeType === COMMENT_NODE ? container.parentNode : container;
+ listenToAllSupportedEvents(rootContainerElement);
+
+ return root;
}
function warnOnInvalidCallback(callback: mixed, callerName: string): void {
@@ -155,7 +165,7 @@ function legacyRenderSubtreeIntoContainer(
container,
forceHydrate,
);
- fiberRoot = root._internalRoot;
+ fiberRoot = root;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
@@ -168,7 +178,7 @@ function legacyRenderSubtreeIntoContainer(
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
- fiberRoot = root._internalRoot;
+ fiberRoot = root;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
@@ -221,7 +231,7 @@ export function hydrate(
) {
if (__DEV__) {
console.error(
- 'ReactDOM.hydrate is no longer supported in React 18. Use createRoot ' +
+ 'ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot ' +
'instead. Until you switch to the new API, your app will behave as ' +
"if it's running React 17. Learn " +
'more: https://reactjs.org/link/switch-to-createroot',
@@ -229,7 +239,7 @@ export function hydrate(
}
invariant(
- isValidContainer(container),
+ isValidContainerLegacy(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
@@ -240,7 +250,7 @@ export function hydrate(
console.error(
'You are calling ReactDOM.hydrate() on a container that was previously ' +
'passed to ReactDOM.createRoot(). This is not supported. ' +
- 'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
+ 'Did you mean to call hydrateRoot(container, element)?',
);
}
}
@@ -269,7 +279,7 @@ export function render(
}
invariant(
- isValidContainer(container),
+ isValidContainerLegacy(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
@@ -300,7 +310,7 @@ export function unstable_renderSubtreeIntoContainer(
callback: ?Function,
) {
invariant(
- isValidContainer(containerNode),
+ isValidContainerLegacy(containerNode),
'Target container is not a DOM element.',
);
invariant(
@@ -318,7 +328,7 @@ export function unstable_renderSubtreeIntoContainer(
export function unmountComponentAtNode(container: Container) {
invariant(
- isValidContainer(container),
+ isValidContainerLegacy(container),
'unmountComponentAtNode(...): Target container is not a DOM element.',
);
@@ -365,7 +375,7 @@ export function unmountComponentAtNode(container: Container) {
// Check if the container itself is a React root node.
const isContainerReactRoot =
container.nodeType === ELEMENT_NODE &&
- isValidContainer(container.parentNode) &&
+ isValidContainerLegacy(container.parentNode) &&
!!container.parentNode._reactRootContainer;
if (hasNonRootReactChild) {
diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js
index dc4c95a41e..ba062fc3f7 100644
--- a/packages/react-dom/src/client/ReactDOMRoot.js
+++ b/packages/react-dom/src/client/ReactDOMRoot.js
@@ -8,7 +8,6 @@
*/
import type {Container} from './ReactDOMHostConfig';
-import type {RootTag} from 'react-reconciler/src/ReactRootTags';
import type {MutableSource, ReactNodeList} from 'shared/ReactTypes';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
@@ -19,7 +18,8 @@ export type RootType = {
...
};
-export type RootOptions = {
+export type CreateRootOptions = {
+ // TODO: Remove these options.
hydrate?: boolean,
hydrationOptions?: {
onHydrated?: (suspenseNode: Comment) => void,
@@ -27,6 +27,18 @@ export type RootOptions = {
mutableSources?: Array>,
...
},
+ // END OF TODO
+ unstable_strictMode?: boolean,
+ unstable_concurrentUpdatesByDefault?: boolean,
+ ...
+};
+
+export type HydrateRootOptions = {
+ // Hydration options
+ hydratedSources?: Array>,
+ onHydrated?: (suspenseNode: Comment) => void,
+ onDeleted?: (suspenseNode: Comment) => void,
+ // Options for all roots
unstable_strictMode?: boolean,
unstable_concurrentUpdatesByDefault?: boolean,
...
@@ -52,20 +64,14 @@ import {
registerMutableSourceForHydration,
} from 'react-reconciler/src/ReactFiberReconciler';
import invariant from 'shared/invariant';
-import {ConcurrentRoot, LegacyRoot} from 'react-reconciler/src/ReactRootTags';
+import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';
-function ReactDOMRoot(container: Container, options: void | RootOptions) {
- this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
+function ReactDOMRoot(internalRoot) {
+ this._internalRoot = internalRoot;
}
-function ReactDOMLegacyRoot(container: Container, options: void | RootOptions) {
- this._internalRoot = createRootImpl(container, LegacyRoot, options);
-}
-
-ReactDOMRoot.prototype.render = ReactDOMLegacyRoot.prototype.render = function(
- children: ReactNodeList,
-): void {
+ReactDOMRoot.prototype.render = function(children: ReactNodeList): void {
const root = this._internalRoot;
if (__DEV__) {
if (typeof arguments[1] === 'function') {
@@ -93,7 +99,7 @@ ReactDOMRoot.prototype.render = ReactDOMLegacyRoot.prototype.render = function(
updateContainer(children, root, null, null);
};
-ReactDOMRoot.prototype.unmount = ReactDOMLegacyRoot.prototype.unmount = function(): void {
+ReactDOMRoot.prototype.unmount = function(): void {
if (__DEV__) {
if (typeof arguments[0] === 'function') {
console.error(
@@ -109,12 +115,17 @@ ReactDOMRoot.prototype.unmount = ReactDOMLegacyRoot.prototype.unmount = function
});
};
-function createRootImpl(
+export function createRoot(
container: Container,
- tag: RootTag,
- options: void | RootOptions,
-) {
- // Tag is either LegacyRoot or Concurrent Root
+ options?: CreateRootOptions,
+): RootType {
+ invariant(
+ isValidContainerLegacy(container),
+ 'createRoot(...): Target container is not a DOM element.',
+ );
+ warnIfReactDOMContainerInDEV(container);
+
+ // TODO: Delete these options
const hydrate = options != null && options.hydrate === true;
const hydrationCallbacks =
(options != null && options.hydrationOptions) || null;
@@ -123,6 +134,58 @@ function createRootImpl(
options.hydrationOptions != null &&
options.hydrationOptions.mutableSources) ||
null;
+ // END TODO
+
+ const isStrictMode = options != null && options.unstable_strictMode === true;
+ let concurrentUpdatesByDefaultOverride = null;
+ if (allowConcurrentByDefault) {
+ concurrentUpdatesByDefaultOverride =
+ options != null && options.unstable_concurrentUpdatesByDefault != null
+ ? options.unstable_concurrentUpdatesByDefault
+ : null;
+ }
+
+ const root = createContainer(
+ container,
+ ConcurrentRoot,
+ hydrate,
+ hydrationCallbacks,
+ isStrictMode,
+ concurrentUpdatesByDefaultOverride,
+ );
+ markContainerAsRoot(root.current, container);
+
+ const rootContainerElement =
+ container.nodeType === COMMENT_NODE ? container.parentNode : container;
+ listenToAllSupportedEvents(rootContainerElement);
+
+ // TODO: Delete this path
+ if (mutableSources) {
+ for (let i = 0; i < mutableSources.length; i++) {
+ const mutableSource = mutableSources[i];
+ registerMutableSourceForHydration(root, mutableSource);
+ }
+ }
+ // END TODO
+
+ return new ReactDOMRoot(root);
+}
+
+export function hydrateRoot(
+ container: Container,
+ initialChildren: ReactNodeList,
+ options?: HydrateRootOptions,
+): RootType {
+ invariant(
+ isValidContainer(container),
+ 'hydrateRoot(...): Target container is not a DOM element.',
+ );
+ warnIfReactDOMContainerInDEV(container);
+
+ // For now we reuse the whole bag of options since they contain
+ // the hydration callbacks.
+ const hydrationCallbacks = options != null ? options : null;
+ const mutableSources = (options != null && options.hydratedSources) || null;
const isStrictMode = options != null && options.unstable_strictMode === true;
let concurrentUpdatesByDefaultOverride = null;
@@ -135,17 +198,15 @@ function createRootImpl(
const root = createContainer(
container,
- tag,
- hydrate,
+ ConcurrentRoot,
+ true, // hydrate
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
markContainerAsRoot(root.current, container);
-
- const rootContainerElement =
- container.nodeType === COMMENT_NODE ? container.parentNode : container;
- listenToAllSupportedEvents(rootContainerElement);
+ // This can't be a comment node since hydration doesn't work on comment nodes anyway.
+ listenToAllSupportedEvents(container);
if (mutableSources) {
for (let i = 0; i < mutableSources.length; i++) {
@@ -154,29 +215,24 @@ function createRootImpl(
}
}
- return root;
+ // Render the initial children
+ updateContainer(initialChildren, root, null, null);
+
+ return new ReactDOMRoot(root);
}
-export function createRoot(
- container: Container,
- options?: RootOptions,
-): RootType {
- invariant(
- isValidContainer(container),
- 'createRoot(...): Target container is not a DOM element.',
+export function isValidContainer(node: any): boolean {
+ return !!(
+ node &&
+ (node.nodeType === ELEMENT_NODE ||
+ node.nodeType === DOCUMENT_NODE ||
+ node.nodeType === DOCUMENT_FRAGMENT_NODE)
);
- warnIfReactDOMContainerInDEV(container);
- return new ReactDOMRoot(container, options);
}
-export function createLegacyRoot(
- container: Container,
- options?: RootOptions,
-): RootType {
- return new ReactDOMLegacyRoot(container, options);
-}
-
-export function isValidContainer(node: mixed): boolean {
+// TODO: Remove this function which also includes comment nodes.
+// We only use it in places that are currently more relaxed.
+export function isValidContainerLegacy(node: any): boolean {
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json
index 538f28756e..d80cd0776e 100644
--- a/scripts/error-codes/codes.json
+++ b/scripts/error-codes/codes.json
@@ -392,5 +392,6 @@
"401": "The stacks must reach the root at the same time. This is a bug in React.",
"402": "The depth must equal at least at zero before reaching the root. This is a bug in React.",
"403": "Tried to pop a Context at the root of the app. This is a bug in React.",
- "404": "Invalid hook call. Hooks can only be called inside of the body of a function component."
+ "404": "Invalid hook call. Hooks can only be called inside of the body of a function component.",
+ "405": "hydrateRoot(...): Target container is not a DOM element."
}