import type {ReactNodeList} from 'shared/ReactTypes';
import type {
RootType,
CreateRootOptions,
HydrateRootOptions,
} from './ReactDOMRoot';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {
Container,
PublicInstance,
} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
import {
createRoot as createRootImpl,
hydrateRoot as hydrateRootImpl,
} from './ReactDOMRoot';
import {disableLegacyMode} from 'shared/ReactFeatureFlags';
import {clearContainer} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
import {
getInstanceFromNode,
isContainerMarkedAsRoot,
markContainerAsRoot,
unmarkContainerAsRoot,
} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
import {isValidContainerLegacy} from 'react-dom-bindings/src/client/ReactDOMContainer';
import {
DOCUMENT_NODE,
ELEMENT_NODE,
COMMENT_NODE,
} from 'react-dom-bindings/src/client/HTMLNodeType';
import {
batchedUpdates,
createContainer,
createHydrationContainer,
findHostInstanceWithNoPortals,
updateContainer,
updateContainerSync,
flushSyncWork,
getPublicRootInstance,
findHostInstance,
findHostInstanceWithWarning,
defaultOnUncaughtError,
defaultOnCaughtError,
} from 'react-reconciler/src/ReactFiberReconciler';
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import {
current as currentOwner,
isRendering,
} from 'react-reconciler/src/ReactCurrentFiber';
import assign from 'shared/assign';
const ReactFiberErrorDialogWWW = require('ReactFiberErrorDialog');
if (typeof ReactFiberErrorDialogWWW.showErrorDialog !== 'function') {
throw new Error(
'Expected ReactFiberErrorDialog.showErrorDialog to be a function.',
);
}
function wwwOnUncaughtError(
error: mixed,
errorInfo: {+componentStack?: ?string},
): void {
const componentStack =
errorInfo.componentStack != null ? errorInfo.componentStack : '';
const logError = ReactFiberErrorDialogWWW.showErrorDialog({
errorBoundary: null,
error,
componentStack,
});
if (logError === false) {
return;
}
defaultOnUncaughtError(error, errorInfo);
}
function wwwOnCaughtError(
error: mixed,
errorInfo: {
+componentStack?: ?string,
+errorBoundary?: ?React$Component<any, any>,
},
): void {
const errorBoundary = errorInfo.errorBoundary;
const componentStack =
errorInfo.componentStack != null ? errorInfo.componentStack : '';
const logError = ReactFiberErrorDialogWWW.showErrorDialog({
errorBoundary,
error,
componentStack,
});
if (logError === false) {
return;
}
defaultOnCaughtError(error, errorInfo);
}
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
return createRootImpl(
container,
assign(
({
onUncaughtError: wwwOnUncaughtError,
onCaughtError: wwwOnCaughtError,
}: any),
options,
),
);
}
export function hydrateRoot(
container: Document | Element,
initialChildren: ReactNodeList,
options?: HydrateRootOptions,
): RootType {
return hydrateRootImpl(
container,
initialChildren,
assign(
({
onUncaughtError: wwwOnUncaughtError,
onCaughtError: wwwOnCaughtError,
}: any),
options,
),
);
}
let topLevelUpdateWarnings;
if (__DEV__) {
topLevelUpdateWarnings = (container: Container) => {
if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
const hostInstance = findHostInstanceWithNoPortals(
container._reactRootContainer.current,
);
if (hostInstance) {
if (hostInstance.parentNode !== container) {
console.error(
'It looks like the React-rendered content of this ' +
'container was removed without using React. This is not ' +
'supported and will cause errors. Instead, call ' +
'ReactDOM.unmountComponentAtNode to empty a container.',
);
}
}
}
const isRootRenderedBySomeReact = !!container._reactRootContainer;
const rootEl = getReactRootElementInContainer(container);
const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
if (hasNonRootReactChild && !isRootRenderedBySomeReact) {
console.error(
'Replacing React-rendered children with a new root ' +
'component. If you intended to update the children of this node, ' +
'you should instead have the existing children update their state ' +
'and render the new components instead of calling ReactDOM.render.',
);
}
};
}
function getReactRootElementInContainer(container: any) {
if (!container) {
return null;
}
if (container.nodeType === DOCUMENT_NODE) {
return container.documentElement;
} else {
return container.firstChild;
}
}
function noopOnRecoverableError() {
}
function legacyCreateRootFromDOMContainer(
container: Container,
initialChildren: ReactNodeList,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
isHydrationContainer: boolean,
): FiberRoot {
if (isHydrationContainer) {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function () {
const instance = getPublicRootInstance(root);
originalCallback.call(instance);
};
}
const root: FiberRoot = createHydrationContainer(
initialChildren,
callback,
container,
LegacyRoot,
null,
false,
false,
'',
wwwOnUncaughtError,
wwwOnCaughtError,
noopOnRecoverableError,
null,
null,
);
container._reactRootContainer = root;
markContainerAsRoot(root.current, container);
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
listenToAllSupportedEvents(rootContainerElement);
flushSyncWork();
return root;
} else {
clearContainer(container);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function () {
const instance = getPublicRootInstance(root);
originalCallback.call(instance);
};
}
const root = createContainer(
container,
LegacyRoot,
null,
false,
false,
'',
wwwOnUncaughtError,
wwwOnCaughtError,
noopOnRecoverableError,
null,
);
container._reactRootContainer = root;
markContainerAsRoot(root.current, container);
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
listenToAllSupportedEvents(rootContainerElement);
updateContainerSync(initialChildren, root, parentComponent, callback);
flushSyncWork();
return root;
}
}
function warnOnInvalidCallback(callback: mixed): void {
if (__DEV__) {
if (callback !== null && typeof callback !== 'function') {
console.error(
'Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
}
}
}
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function,
): React$Component<any, any> | PublicInstance | null {
if (__DEV__) {
topLevelUpdateWarnings(container);
warnOnInvalidCallback(callback === undefined ? null : callback);
}
const maybeRoot = container._reactRootContainer;
let root: FiberRoot;
if (!maybeRoot) {
root = legacyCreateRootFromDOMContainer(
container,
children,
parentComponent,
callback,
forceHydrate,
);
} else {
root = maybeRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function () {
const instance = getPublicRootInstance(root);
originalCallback.call(instance);
};
}
updateContainer(children, root, parentComponent, callback);
}
return getPublicRootInstance(root);
}
export function findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {
if (__DEV__) {
const owner = currentOwner;
if (owner !== null && isRendering && owner.stateNode !== null) {
const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
if (!warnedAboutRefsInRender) {
console.error(
'%s is accessing findDOMNode inside its render(). ' +
'render() should be a pure function of props and state. It should ' +
'never access something that requires stale data from the previous ' +
'render, such as refs. Move this logic to componentDidMount and ' +
'componentDidUpdate instead.',
getComponentNameFromType(owner.type) || 'A component',
);
}
owner.stateNode._warnedAboutRefsInRender = true;
}
}
if (componentOrElement == null) {
return null;
}
if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
return (componentOrElement: any);
}
if (__DEV__) {
return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
}
return findHostInstance(componentOrElement);
}
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
): React$Component<any, any> | PublicInstance | null {
if (disableLegacyMode) {
if (__DEV__) {
console.error(
'ReactDOM.render was removed in React 19. Use createRoot instead.',
);
}
throw new Error('ReactDOM: Unsupported Legacy Mode API.');
}
if (__DEV__) {
console.error(
'ReactDOM.render has not been supported since React 18. Use createRoot ' +
'instead. Until you switch to the new API, your app will behave as ' +
"if it's running React 17. Learn " +
'more: https://react.dev/link/switch-to-createroot',
);
}
if (!isValidContainerLegacy(container)) {
throw new Error('Target container is not a DOM element.');
}
if (__DEV__) {
const isModernRoot =
isContainerMarkedAsRoot(container) &&
container._reactRootContainer === undefined;
if (isModernRoot) {
console.error(
'You are calling ReactDOM.render() on a container that was previously ' +
'passed to ReactDOMClient.createRoot(). This is not supported. ' +
'Did you mean to call root.render(element)?',
);
}
}
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
export function unmountComponentAtNode(container: Container): boolean {
if (disableLegacyMode) {
if (__DEV__) {
console.error(
'unmountComponentAtNode was removed in React 19. Use root.unmount() instead.',
);
}
throw new Error('ReactDOM: Unsupported Legacy Mode API.');
}
if (!isValidContainerLegacy(container)) {
throw new Error('Target container is not a DOM element.');
}
if (__DEV__) {
const isModernRoot =
isContainerMarkedAsRoot(container) &&
container._reactRootContainer === undefined;
if (isModernRoot) {
console.error(
'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?',
);
}
}
if (container._reactRootContainer) {
const root = container._reactRootContainer;
if (__DEV__) {
const rootEl = getReactRootElementInContainer(container);
const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
if (renderedByDifferentReact) {
console.error(
"unmountComponentAtNode(): The node you're attempting to unmount " +
'was rendered by another copy of React.',
);
}
}
updateContainerSync(null, root, null, null);
flushSyncWork();
container._reactRootContainer = null;
unmarkContainerAsRoot(container);
return true;
} else {
if (__DEV__) {
const rootEl = getReactRootElementInContainer(container);
const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
const isContainerReactRoot =
container.nodeType === ELEMENT_NODE &&
isValidContainerLegacy(container.parentNode) &&
!!container.parentNode._reactRootContainer;
if (hasNonRootReactChild) {
console.error(
"unmountComponentAtNode(): The node you're attempting to unmount " +
'was rendered by React and is not a top-level container. %s',
isContainerReactRoot
? 'You may have accidentally passed in a React root node instead ' +
'of its container.'
: 'Instead, have the parent component update its state and ' +
'rerender in order to remove this component.',
);
}
}
return false;
}
}
export {batchedUpdates as unstable_batchedUpdates};