Move ReactDOMLegacy implementation into RootFB (#28656)

Only the FB entry point has legacy mode now so we can move the remaining
code in there.

Also enable disableLegacyMode in modern www builds since it doesn't
expose those entry points.

Now dependent on #28709.

---------

Co-authored-by: Josh Story <story@hey.com>
This commit is contained in:
Sebastian Markbåge
2024-04-02 21:56:23 -04:00
committed by GitHub
parent 5de8703646
commit 8f55a6aa57
21 changed files with 206 additions and 539 deletions

View File

@@ -69,6 +69,7 @@ function mountStrictApp(App) {
}
function mountLegacyApp(App: () => React$Node) {
// $FlowFixMe[prop-missing]: These are removed in 19.
const {render, unmountComponentAtNode} = require('react-dom');
function LegacyRender() {
@@ -77,8 +78,10 @@ function mountLegacyApp(App: () => React$Node) {
const container = createContainer();
// $FlowFixMe[not-a-function]: These are removed in 19.
render(createElement(LegacyRender), container);
// $FlowFixMe: These are removed in 19.
unmountFunctions.push(() => unmountComponentAtNode(container));
}

View File

@@ -15,6 +15,7 @@ function mountApp(App: () => React$Node) {
((document.body: any): HTMLBodyElement).appendChild(container);
// $FlowFixMe[prop-missing]: These are removed in 19.
ReactDOM.render(<App />, container);
}
function mountTestApp() {

View File

@@ -20,11 +20,8 @@ Object.assign((Internals: any), {
export {
createPortal,
findDOMNode,
flushSync,
unmountComponentAtNode,
unstable_createEventHandle,
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,
@@ -42,6 +39,9 @@ export {
hydrateRoot,
render,
unstable_batchedUpdates,
findDOMNode,
unstable_renderSubtreeIntoContainer,
unmountComponentAtNode,
} from './src/client/ReactDOMRootFB';
export {Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED};

View File

@@ -15,11 +15,8 @@ export {
createRoot,
hydrateRoot,
flushSync,
render,
unmountComponentAtNode,
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,

View File

@@ -13,10 +13,7 @@ export {
createRoot,
hydrateRoot,
flushSync,
render,
unmountComponentAtNode,
unstable_batchedUpdates,
unstable_renderSubtreeIntoContainer,
useFormStatus,
useFormState,
prefetchDNS,

View File

@@ -7,7 +7,6 @@
* @flow
*/
import type {FindDOMNodeType} from './client/ReactDOMLegacy.js';
import type {HostDispatcher} from './shared/ReactDOMTypes';
type InternalsType = {
@@ -16,7 +15,11 @@ type InternalsType = {
ReactDOMCurrentDispatcher: {
current: HostDispatcher,
},
findDOMNode: null | FindDOMNodeType,
findDOMNode:
| null
| ((
componentOrElement: React$Component<any, any>,
) => null | Element | Text),
};
function noop() {}

View File

@@ -14,7 +14,6 @@ let act;
let React;
let ReactDOM;
let ReactDOMClient;
let findDOMNode;
const clone = function (o) {
return JSON.parse(JSON.stringify(o));
@@ -95,8 +94,6 @@ describe('ReactComponentLifeCycle', () => {
React = require('react');
ReactDOM = require('react-dom');
findDOMNode =
ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode;
ReactDOMClient = require('react-dom/client');
});
@@ -376,6 +373,7 @@ describe('ReactComponentLifeCycle', () => {
expect(instance.updater.isMounted(instance)).toBe(false);
});
// @gate www && !disableLegacyMode
it('warns if legacy findDOMNode is used inside render', async () => {
class Component extends React.Component {
state = {isMounted: false};
@@ -384,7 +382,7 @@ describe('ReactComponentLifeCycle', () => {
}
render() {
if (this.state.isMounted) {
expect(findDOMNode(this).tagName).toBe('DIV');
expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV');
}
return <div />;
}

View File

@@ -103,9 +103,9 @@ describe('ReactDeprecationWarnings', () => {
});
}
}
expect(() => {
ReactNoop.renderLegacySyncRoot(<Component />);
}).toErrorDev([
ReactNoop.render(<Component />);
await expect(async () => await waitForAll([])).toErrorDev([
'Component "Component" contains the string ref "refComponent". Support for string refs will be removed in a future major release.',
]);
await waitForAll([]);

View File

@@ -586,6 +586,7 @@ describe('ReactTestUtils', () => {
});
// @gate !disableDOMTestUtils
// @gate !disableLegacyMode
it('should call setState callback with no arguments', async () => {
let mockArgs;
class Component extends React.Component {

View File

@@ -11,16 +11,15 @@
const React = require('react');
const ReactDOM = require('react-dom');
const findDOMNode =
ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode;
const StrictMode = React.StrictMode;
describe('findDOMNode', () => {
// @gate www && !disableLegacyMode
it('findDOMNode should return null if passed null', () => {
expect(findDOMNode(null)).toBe(null);
expect(ReactDOM.findDOMNode(null)).toBe(null);
});
// @gate !disableLegacyMode
// @gate www && !disableLegacyMode
it('findDOMNode should find dom element', () => {
class MyNode extends React.Component {
render() {
@@ -34,13 +33,13 @@ describe('findDOMNode', () => {
const container = document.createElement('div');
const myNode = ReactDOM.render(<MyNode />, container);
const myDiv = findDOMNode(myNode);
const mySameDiv = findDOMNode(myDiv);
const myDiv = ReactDOM.findDOMNode(myNode);
const mySameDiv = ReactDOM.findDOMNode(myDiv);
expect(myDiv.tagName).toBe('DIV');
expect(mySameDiv).toBe(myDiv);
});
// @gate !disableLegacyMode
// @gate www && !disableLegacyMode
it('findDOMNode should find dom element after an update from null', () => {
function Bar({flag}) {
if (flag) {
@@ -57,23 +56,24 @@ describe('findDOMNode', () => {
const container = document.createElement('div');
const myNodeA = ReactDOM.render(<MyNode />, container);
const a = findDOMNode(myNodeA);
const a = ReactDOM.findDOMNode(myNodeA);
expect(a).toBe(null);
const myNodeB = ReactDOM.render(<MyNode flag={true} />, container);
expect(myNodeA === myNodeB).toBe(true);
const b = findDOMNode(myNodeB);
const b = ReactDOM.findDOMNode(myNodeB);
expect(b.tagName).toBe('SPAN');
});
// @gate www && !disableLegacyMode
it('findDOMNode should reject random objects', () => {
expect(function () {
findDOMNode({foo: 'bar'});
ReactDOM.findDOMNode({foo: 'bar'});
}).toThrowError('Argument appears to not be a ReactComponent. Keys: foo');
});
// @gate !disableLegacyMode
// @gate www && !disableLegacyMode
it('findDOMNode should reject unmounted objects with render func', () => {
class Foo extends React.Component {
render() {
@@ -85,16 +85,16 @@ describe('findDOMNode', () => {
const inst = ReactDOM.render(<Foo />, container);
ReactDOM.unmountComponentAtNode(container);
expect(() => findDOMNode(inst)).toThrowError(
expect(() => ReactDOM.findDOMNode(inst)).toThrowError(
'Unable to find node on an unmounted component.',
);
});
// @gate !disableLegacyMode
// @gate www && !disableLegacyMode
it('findDOMNode should not throw an error when called within a component that is not mounted', () => {
class Bar extends React.Component {
UNSAFE_componentWillMount() {
expect(findDOMNode(this)).toBeNull();
expect(ReactDOM.findDOMNode(this)).toBeNull();
}
render() {
@@ -107,7 +107,7 @@ describe('findDOMNode', () => {
}).not.toThrow();
});
// @gate !disableLegacyMode
// @gate www && !disableLegacyMode
it('findDOMNode should warn if used to find a host component inside StrictMode', () => {
let parent = undefined;
let child = undefined;
@@ -129,7 +129,7 @@ describe('findDOMNode', () => {
);
let match;
expect(() => (match = findDOMNode(parent))).toErrorDev([
expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([
'Warning: findDOMNode is deprecated in StrictMode. ' +
'findDOMNode was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' +
'Instead, add a ref directly to the element you want to reference. ' +
@@ -141,7 +141,7 @@ describe('findDOMNode', () => {
expect(match).toBe(child);
});
// @gate !disableLegacyMode
// @gate www && !disableLegacyMode
it('findDOMNode should warn if passed a component that is inside StrictMode', () => {
let parent = undefined;
let child = undefined;
@@ -162,7 +162,7 @@ describe('findDOMNode', () => {
);
let match;
expect(() => (match = findDOMNode(parent))).toErrorDev([
expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([
'Warning: findDOMNode is deprecated in StrictMode. ' +
'findDOMNode was passed an instance of IsInStrictMode which is inside StrictMode. ' +
'Instead, add a ref directly to the element you want to reference. ' +

View File

@@ -8,22 +8,12 @@
*/
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
Container,
PublicInstance,
} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
import type {
RootType,
HydrateRootOptions,
CreateRootOptions,
} from './ReactDOMRoot';
import {
findDOMNode,
render,
unstable_renderSubtreeIntoContainer,
unmountComponentAtNode,
} from './ReactDOMLegacy';
import {
createRoot as createRootImpl,
hydrateRoot as hydrateRootImpl,
@@ -35,6 +25,7 @@ import {
flushSync as flushSyncWithoutWarningIfAlreadyRendering,
isAlreadyRendering,
injectIntoDevTools,
findHostInstance,
} from 'react-reconciler/src/ReactFiberReconciler';
import {runWithPriority} from 'react-reconciler/src/ReactEventPriorities';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
@@ -99,20 +90,6 @@ function createPortal(
return createPortalImpl(children, container, null, key);
}
function renderSubtreeIntoContainer(
parentComponent: React$Component<any, any>,
element: React$Element<any>,
containerNode: Container,
callback: ?Function,
): React$Component<any, any> | PublicInstance | null {
return unstable_renderSubtreeIntoContainer(
parentComponent,
element,
containerNode,
callback,
);
}
function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
@@ -163,6 +140,12 @@ function flushSync<R>(fn: (() => R) | void): R | void {
return flushSyncWithoutWarningIfAlreadyRendering(fn);
}
function findDOMNode(
componentOrElement: React$Component<any, any>,
): null | Element | Text {
return findHostInstance(componentOrElement);
}
// Expose findDOMNode on internals
Internals.findDOMNode = findDOMNode;
@@ -178,15 +161,9 @@ export {
unstable_batchedUpdates,
flushSync,
ReactVersion as version,
// Disabled behind disableLegacyReactDOMAPIs
findDOMNode,
render,
unmountComponentAtNode,
// exposeConcurrentModeAPIs
createRoot,
hydrateRoot,
// Disabled behind disableUnstableRenderSubtreeIntoContainer
renderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer,
// enableCreateEventHandleAPI
createEventHandle as unstable_createEventHandle,
// TODO: Remove this once callers migrate to alternatives.

View File

@@ -1,434 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {
Container,
PublicInstance,
} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
import type {ReactNodeList} from 'shared/ReactTypes';
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 './ReactDOMRoot';
import {
DOCUMENT_NODE,
ELEMENT_NODE,
COMMENT_NODE,
} from 'react-dom-bindings/src/client/HTMLNodeType';
import {
createContainer,
createHydrationContainer,
findHostInstanceWithNoPortals,
updateContainer,
flushSync,
getPublicRootInstance,
findHostInstance,
findHostInstanceWithWarning,
defaultOnUncaughtError,
defaultOnCaughtError,
} from 'react-reconciler/src/ReactFiberReconciler';
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {has as hasInstance} from 'shared/ReactInstanceMap';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
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() {
// This isn't reachable because onRecoverableError isn't called in the
// legacy API.
}
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, // hydrationCallbacks
false, // isStrictMode
false, // concurrentUpdatesByDefaultOverride,
'', // identifierPrefix
defaultOnUncaughtError,
defaultOnCaughtError,
noopOnRecoverableError,
// TODO(luna) Support hydration later
null,
null,
);
container._reactRootContainer = root;
markContainerAsRoot(root.current, container);
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
// $FlowFixMe[incompatible-call]
listenToAllSupportedEvents(rootContainerElement);
flushSync();
return root;
} else {
// First clear any existing content.
clearContainer(container);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function () {
const instance = getPublicRootInstance(root);
originalCallback.call(instance);
};
}
const root = createContainer(
container,
LegacyRoot,
null, // hydrationCallbacks
false, // isStrictMode
false, // concurrentUpdatesByDefaultOverride,
'', // identifierPrefix
defaultOnUncaughtError,
defaultOnCaughtError,
noopOnRecoverableError,
null, // transitionCallbacks
);
container._reactRootContainer = root;
markContainerAsRoot(root.current, container);
const rootContainerElement =
container.nodeType === COMMENT_NODE ? container.parentNode : container;
// $FlowFixMe[incompatible-call]
listenToAllSupportedEvents(rootContainerElement);
// Initial mount should not be batched.
flushSync(() => {
updateContainer(initialChildren, root, parentComponent, callback);
});
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) {
// Initial mount
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);
};
}
// Update
updateContainer(children, root, parentComponent, callback);
}
return getPublicRootInstance(root);
}
export type FindDOMNodeType = typeof findDOMNode;
export function findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {
if (__DEV__) {
const owner = (ReactCurrentOwner.current: any);
if (owner !== null && 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 unstable_renderSubtreeIntoContainer(
parentComponent: React$Component<any, any>,
element: React$Element<any>,
containerNode: Container,
callback: ?Function,
): React$Component<any, any> | PublicInstance | null {
if (disableLegacyMode) {
if (__DEV__) {
console.error(
'ReactDOM.unstable_renderSubtreeIntoContainer() was removed in React 19. Consider using a portal instead.',
);
}
throw new Error('ReactDOM: Unsupported Legacy Mode API.');
}
if (__DEV__) {
console.error(
'ReactDOM.unstable_renderSubtreeIntoContainer() has not been supported ' +
'since React 18. Consider using a portal instead. Until you switch to ' +
"the createRoot API, your app will behave as if it's running React " +
'17. Learn more: https://react.dev/link/switch-to-createroot',
);
}
if (!isValidContainerLegacy(containerNode)) {
throw new Error('Target container is not a DOM element.');
}
if (parentComponent == null || !hasInstance(parentComponent)) {
throw new Error('parentComponent must be a valid React Component');
}
return legacyRenderSubtreeIntoContainer(
parentComponent,
element,
containerNode,
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) {
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.',
);
}
}
// Unmount should not be batched.
flushSync(() => {
legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
// $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
container._reactRootContainer = null;
unmarkContainerAsRoot(container);
});
});
// If you call unmountComponentAtNode twice in quick succession, you'll
// get `true` twice. That's probably fine?
return true;
} else {
if (__DEV__) {
const rootEl = getReactRootElementInContainer(container);
const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
// Check if the container itself is a React root node.
const isContainerReactRoot =
container.nodeType === ELEMENT_NODE &&
isValidContainerLegacy(container.parentNode) &&
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-use]
!!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;
}
}

View File

@@ -33,11 +33,13 @@ import {
getInstanceFromNode,
isContainerMarkedAsRoot,
markContainerAsRoot,
unmarkContainerAsRoot,
} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
import {isValidContainerLegacy} from './ReactDOMRoot';
import {
DOCUMENT_NODE,
ELEMENT_NODE,
COMMENT_NODE,
} from 'react-dom-bindings/src/client/HTMLNodeType';
@@ -49,12 +51,17 @@ import {
updateContainer,
flushSync,
getPublicRootInstance,
findHostInstance,
findHostInstanceWithWarning,
defaultOnUncaughtError,
defaultOnCaughtError,
} from 'react-reconciler/src/ReactFiberReconciler';
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import {has as hasInstance} from 'shared/ReactInstanceMap';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import assign from 'shared/assign';
// Provided by www
@@ -146,6 +153,8 @@ export function hydrateRoot(
);
}
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
let topLevelUpdateWarnings;
if (__DEV__) {
@@ -331,6 +340,38 @@ function legacyRenderSubtreeIntoContainer(
return getPublicRootInstance(root);
}
export function findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {
if (__DEV__) {
const owner = (ReactCurrentOwner.current: any);
if (owner !== null && 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,
@@ -418,4 +459,82 @@ export function unstable_renderSubtreeIntoContainer(
);
}
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) {
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.',
);
}
}
// Unmount should not be batched.
flushSync(() => {
legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
// $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
container._reactRootContainer = null;
unmarkContainerAsRoot(container);
});
});
// If you call unmountComponentAtNode twice in quick succession, you'll
// get `true` twice. That's probably fine?
return true;
} else {
if (__DEV__) {
const rootEl = getReactRootElementInContainer(container);
const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
// Check if the container itself is a React root node.
const isContainerReactRoot =
container.nodeType === ELEMENT_NODE &&
isValidContainerLegacy(container.parentNode) &&
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-use]
!!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};

View File

@@ -10,11 +10,8 @@
export {
createPortal,
flushSync,
render,
unmountComponentAtNode,
unstable_batchedUpdates,
unstable_createEventHandle,
unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,

View File

@@ -10,10 +10,7 @@
export {
createPortal,
flushSync,
render,
unmountComponentAtNode,
unstable_batchedUpdates,
unstable_renderSubtreeIntoContainer,
useFormStatus,
useFormState,
prefetchDNS,

View File

@@ -393,7 +393,7 @@ describe('ReactScope', () => {
});
// @gate www
it('DO_NOT_USE_queryAllNodes() works as intended', () => {
it('DO_NOT_USE_queryAllNodes() works as intended', async () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
@@ -417,20 +417,25 @@ describe('ReactScope', () => {
);
}
const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
createNodeMock: element => {
return element;
},
});
let renderer;
await act(
() =>
(renderer = ReactTestRenderer.create(<Test toggle={true} />, {
createNodeMock: element => {
return element;
},
unstable_isConcurrent: true,
})),
);
let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
renderer.update(<Test toggle={false} />);
await act(() => renderer.update(<Test toggle={false} />));
nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
});
// @gate www
it('DO_NOT_USE_queryFirstNode() works as intended', () => {
it('DO_NOT_USE_queryFirstNode() works as intended', async () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
@@ -454,20 +459,26 @@ describe('ReactScope', () => {
);
}
const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
createNodeMock: element => {
return element;
},
});
let renderer;
await act(
() =>
(renderer = ReactTestRenderer.create(<Test toggle={true} />, {
createNodeMock: element => {
return element;
},
unstable_isConcurrent: true,
})),
);
let node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
expect(node).toEqual(divRef.current);
renderer.update(<Test toggle={false} />);
await act(() => renderer.update(<Test toggle={false} />));
node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
expect(node).toEqual(aRef.current);
});
// @gate www
it('containsNode() works as intended', () => {
it('containsNode() works as intended', async () => {
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
const divRef = React.createRef();
@@ -500,23 +511,28 @@ describe('ReactScope', () => {
);
}
const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
createNodeMock: element => {
return element;
},
});
let renderer;
await act(
() =>
(renderer = ReactTestRenderer.create(<Test toggle={true} />, {
createNodeMock: element => {
return element;
},
unstable_isConcurrent: true,
})),
);
expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
renderer.update(<Test toggle={false} />);
await act(() => renderer.update(<Test toggle={false} />));
expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
expect(scopeRef.current.containsNode(emRef.current)).toBe(true);
renderer.update(<Test toggle={true} />);
await act(() => renderer.update(<Test toggle={true} />));
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
});
});

View File

@@ -378,7 +378,7 @@ describe('ReactSuspense', () => {
expect(container.textContent).toEqual('AB');
});
// @gate forceConcurrentByDefaultForTesting
// @gate !disableLegacyMode && forceConcurrentByDefaultForTesting
it(
'interrupts current render when something suspends with a ' +
"delay and we've already skipped over a lower priority update in " +

View File

@@ -10,12 +10,10 @@
'use strict';
let React;
let ReactDOM;
let ReactDOMClient;
let JSXRuntime;
let JSXDEVRuntime;
let act;
let findDOMNode;
// NOTE: Prefer to call the JSXRuntime directly in these tests so we can be
// certain that we are testing the runtime behavior, as opposed to the Babel
@@ -27,11 +25,8 @@ describe('ReactJSXRuntime', () => {
React = require('react');
JSXRuntime = require('react/jsx-runtime');
JSXDEVRuntime = require('react/jsx-dev-runtime');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
findDOMNode =
ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode;
});
it('allows static methods to be called using the type property', () => {
@@ -133,9 +128,9 @@ describe('ReactJSXRuntime', () => {
const outer = container.firstChild;
if (__DEV__) {
expect(findDOMNode(outer).className).toBe('moo');
expect(outer.className).toBe('moo');
} else {
expect(findDOMNode(outer).className).toBe('quack');
expect(outer.className).toBe('quack');
}
});

View File

@@ -178,7 +178,7 @@ export const enableReactTestRendererWarning = __NEXT_MAJOR__;
// Disables legacy mode
// This allows us to land breaking changes to remove legacy mode APIs in experimental builds
// before removing them in stable in the next Major
export const disableLegacyMode = __NEXT_MAJOR__;
export const disableLegacyMode = true;
export const disableDOMTestUtils = __NEXT_MAJOR__;

View File

@@ -88,7 +88,7 @@ const __NEXT_MAJOR__ = __EXPERIMENTAL__;
export const enableRefAsProp = __NEXT_MAJOR__;
export const disableStringRefs = __NEXT_MAJOR__;
export const enableBigIntSupport = __NEXT_MAJOR__;
export const disableLegacyMode = __NEXT_MAJOR__;
export const disableLegacyMode = true;
export const disableLegacyContext = __NEXT_MAJOR__;
export const disableDOMTestUtils = __NEXT_MAJOR__;
export const enableRenderableContext = __NEXT_MAJOR__;

View File

@@ -113,7 +113,7 @@ export const useModernStrictMode = true;
// because JSX is an extremely hot path.
export const disableStringRefs = false;
export const disableLegacyMode = false;
export const disableLegacyMode = __EXPERIMENTAL__;
export const disableDOMTestUtils = false;