Skip to content

Commit

Permalink
Remove legacy ReactDOM APIs behind feature flag
Browse files Browse the repository at this point in the history
  • Loading branch information
kassens committed Dec 12, 2023
1 parent 0cdfef1 commit 111edd9
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 135 deletions.
7 changes: 4 additions & 3 deletions packages/react-dom/src/client/ReactDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,15 +170,16 @@ export {
batchedUpdates as unstable_batchedUpdates,
flushSync,
ReactVersion as version,
// Disabled behind disableLegacyReactDOMAPIs
findDOMNode,
// Disabled behind disableLegacyReactDOMRenderAPIs
hydrate,
// Disabled behind disableLegacyReactDOMRenderAPIs
render,
// Disabled behind disableLegacyReactDOMRenderAPIs
unmountComponentAtNode,
// exposeConcurrentModeAPIs
createRoot,
hydrateRoot,
// Disabled behind disableUnstableRenderSubtreeIntoContainer
// Disabled behind disableLegacyReactDOMRenderAPIs
renderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer,
// enableCreateEventHandleAPI
createEventHandle as unstable_createEventHandle,
Expand Down
289 changes: 160 additions & 129 deletions packages/react-dom/src/client/ReactDOMLegacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {has as hasInstance} from 'shared/ReactInstanceMap';
import {disableLegacyReactDOMRenderAPIs} from 'shared/ReactFeatureFlags';

const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;

Expand Down Expand Up @@ -265,78 +266,92 @@ export function hydrate(
container: Container,
callback: ?Function,
): React$Component<any, any> | PublicInstance | null {
if (__DEV__) {
console.error(
'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',
if (disableLegacyReactDOMRenderAPIs) {
throw new Error(
'ReactDOM.hydrate is no longer supported. Use hydrateRoot ' +
'instead. Learn more: https://reactjs.org/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) {
} else {
if (__DEV__) {
console.error(
'You are calling ReactDOM.hydrate() on a container that was previously ' +
'passed to ReactDOMClient.createRoot(). This is not supported. ' +
'Did you mean to call hydrateRoot(container, element)?',
'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',
);
}

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.hydrate() on a container that was previously ' +
'passed to ReactDOMClient.createRoot(). This is not supported. ' +
'Did you mean to call hydrateRoot(container, element)?',
);
}
}
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
}
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
}

export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
): React$Component<any, any> | PublicInstance | null {
if (__DEV__) {
console.error(
'ReactDOM.render is no longer supported in 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://reactjs.org/link/switch-to-createroot',
if (disableLegacyReactDOMRenderAPIs) {
throw new Error(
'ReactDOM.render is no longer supported. Use hydrateRoot instead. ' +
'Learn more: https://reactjs.org/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) {
} else {
if (__DEV__) {
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)?',
'ReactDOM.render is no longer supported in 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://reactjs.org/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,
);
}
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}

export function unstable_renderSubtreeIntoContainer(
Expand All @@ -345,100 +360,116 @@ export function unstable_renderSubtreeIntoContainer(
containerNode: Container,
callback: ?Function,
): React$Component<any, any> | PublicInstance | null {
if (__DEV__) {
console.error(
'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported ' +
'in 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://reactjs.org/link/switch-to-createroot',
if (disableLegacyReactDOMRenderAPIs) {
throw new Error(
'ReactDOM.unstable_renderSubtreeIntoContainer is no longer supported. ' +
'Use a portal instead. Learn more: ' +
'https://reactjs.org/link/switch-to-createroot',
);
}
} else {
if (__DEV__) {
console.error(
'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported ' +
'in 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://reactjs.org/link/switch-to-createroot',
);
}

if (!isValidContainerLegacy(containerNode)) {
throw new Error('Target container is not a DOM element.');
}
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');
}
if (parentComponent == null || !hasInstance(parentComponent)) {
throw new Error('parentComponent must be a valid React Component');
}

return legacyRenderSubtreeIntoContainer(
parentComponent,
element,
containerNode,
false,
callback,
);
return legacyRenderSubtreeIntoContainer(
parentComponent,
element,
containerNode,
false,
callback,
);
}
}

export function unmountComponentAtNode(container: Container): boolean {
if (!isValidContainerLegacy(container)) {
if (disableLegacyReactDOMRenderAPIs) {
throw new Error(
'unmountComponentAtNode(...): Target container is not a DOM element.',
'ReactDOM.unmountComponentAtNode is no longer supported. Did you mean ' +
'to call root.unmount()? Learn more: ' +
'https://reactjs.org/link/switch-to-createroot',
);
}

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()?',
} else {
if (!isValidContainerLegacy(container)) {
throw new Error(
'unmountComponentAtNode(...): Target container is not a DOM element.',
);
}
}

if (container._reactRootContainer) {
if (__DEV__) {
const rootEl = getReactRootElementInContainer(container);
const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
if (renderedByDifferentReact) {
const isModernRoot =
isContainerMarkedAsRoot(container) &&
container._reactRootContainer === undefined;
if (isModernRoot) {
console.error(
"unmountComponentAtNode(): The node you're attempting to unmount " +
'was rendered by another copy of React.',
'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()?',
);
}
}

// 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 (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.',
);
// 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;
return false;
}
}
}
4 changes: 4 additions & 0 deletions packages/shared/ReactFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ export const enableCustomElementPropertySupport = __EXPERIMENTAL__;
// Disables children for <textarea> elements
export const disableTextareaChildren = false;

// Removes legacy render API from react-dom, including render, hydrate, hydrate
// and unmountComponentAtNode.
export const disableLegacyReactDOMRenderAPIs = true;

// -----------------------------------------------------------------------------
// Debugging and DevTools
// -----------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const enableSuspenseCallback = false;
export const disableLegacyContext = false;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableLegacyReactDOMRenderAPIs = true;
export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseAvoidThisFallbackFizz = false;
export const enableCPUSuspense = true;
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-oss.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const enableSuspenseCallback = false;
export const disableLegacyContext = false;
export const enableTrustedTypesIntegration = false;
export const disableTextareaChildren = false;
export const disableLegacyReactDOMRenderAPIs = true;
export const disableModulePatternComponents = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseAvoidThisFallbackFizz = false;
Expand Down
Loading

0 comments on commit 111edd9

Please sign in to comment.