Skip to content

Commit 111edd9

Browse files
committed
Remove legacy ReactDOM APIs behind feature flag
1 parent 0cdfef1 commit 111edd9

12 files changed

Lines changed: 196 additions & 135 deletions

packages/react-dom/src/client/ReactDOM.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,15 +170,16 @@ export {
170170
batchedUpdates as unstable_batchedUpdates,
171171
flushSync,
172172
ReactVersion as version,
173-
// Disabled behind disableLegacyReactDOMAPIs
174173
findDOMNode,
174+
// Disabled behind disableLegacyReactDOMRenderAPIs
175175
hydrate,
176+
// Disabled behind disableLegacyReactDOMRenderAPIs
176177
render,
178+
// Disabled behind disableLegacyReactDOMRenderAPIs
177179
unmountComponentAtNode,
178-
// exposeConcurrentModeAPIs
179180
createRoot,
180181
hydrateRoot,
181-
// Disabled behind disableUnstableRenderSubtreeIntoContainer
182+
// Disabled behind disableLegacyReactDOMRenderAPIs
182183
renderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer,
183184
// enableCreateEventHandleAPI
184185
createEventHandle as unstable_createEventHandle,

packages/react-dom/src/client/ReactDOMLegacy.js

Lines changed: 160 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
4343
import getComponentNameFromType from 'shared/getComponentNameFromType';
4444
import ReactSharedInternals from 'shared/ReactSharedInternals';
4545
import {has as hasInstance} from 'shared/ReactInstanceMap';
46+
import {disableLegacyReactDOMRenderAPIs} from 'shared/ReactFeatureFlags';
4647

4748
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
4849

@@ -265,78 +266,92 @@ export function hydrate(
265266
container: Container,
266267
callback: ?Function,
267268
): React$Component<any, any> | PublicInstance | null {
268-
if (__DEV__) {
269-
console.error(
270-
'ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot ' +
271-
'instead. Until you switch to the new API, your app will behave as ' +
272-
"if it's running React 17. Learn " +
273-
'more: https://reactjs.org/link/switch-to-createroot',
269+
if (disableLegacyReactDOMRenderAPIs) {
270+
throw new Error(
271+
'ReactDOM.hydrate is no longer supported. Use hydrateRoot ' +
272+
'instead. Learn more: https://reactjs.org/link/switch-to-createroot',
274273
);
275-
}
276-
277-
if (!isValidContainerLegacy(container)) {
278-
throw new Error('Target container is not a DOM element.');
279-
}
280-
281-
if (__DEV__) {
282-
const isModernRoot =
283-
isContainerMarkedAsRoot(container) &&
284-
container._reactRootContainer === undefined;
285-
if (isModernRoot) {
274+
} else {
275+
if (__DEV__) {
286276
console.error(
287-
'You are calling ReactDOM.hydrate() on a container that was previously ' +
288-
'passed to ReactDOMClient.createRoot(). This is not supported. ' +
289-
'Did you mean to call hydrateRoot(container, element)?',
277+
'ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot ' +
278+
'instead. Until you switch to the new API, your app will behave as ' +
279+
"if it's running React 17. Learn " +
280+
'more: https://reactjs.org/link/switch-to-createroot',
290281
);
291282
}
283+
284+
if (!isValidContainerLegacy(container)) {
285+
throw new Error('Target container is not a DOM element.');
286+
}
287+
288+
if (__DEV__) {
289+
const isModernRoot =
290+
isContainerMarkedAsRoot(container) &&
291+
container._reactRootContainer === undefined;
292+
if (isModernRoot) {
293+
console.error(
294+
'You are calling ReactDOM.hydrate() on a container that was previously ' +
295+
'passed to ReactDOMClient.createRoot(). This is not supported. ' +
296+
'Did you mean to call hydrateRoot(container, element)?',
297+
);
298+
}
299+
}
300+
// TODO: throw or warn if we couldn't hydrate?
301+
return legacyRenderSubtreeIntoContainer(
302+
null,
303+
element,
304+
container,
305+
true,
306+
callback,
307+
);
292308
}
293-
// TODO: throw or warn if we couldn't hydrate?
294-
return legacyRenderSubtreeIntoContainer(
295-
null,
296-
element,
297-
container,
298-
true,
299-
callback,
300-
);
301309
}
302310

303311
export function render(
304312
element: React$Element<any>,
305313
container: Container,
306314
callback: ?Function,
307315
): React$Component<any, any> | PublicInstance | null {
308-
if (__DEV__) {
309-
console.error(
310-
'ReactDOM.render is no longer supported in React 18. Use createRoot ' +
311-
'instead. Until you switch to the new API, your app will behave as ' +
312-
"if it's running React 17. Learn " +
313-
'more: https://reactjs.org/link/switch-to-createroot',
316+
if (disableLegacyReactDOMRenderAPIs) {
317+
throw new Error(
318+
'ReactDOM.render is no longer supported. Use hydrateRoot instead. ' +
319+
'Learn more: https://reactjs.org/link/switch-to-createroot',
314320
);
315-
}
316-
317-
if (!isValidContainerLegacy(container)) {
318-
throw new Error('Target container is not a DOM element.');
319-
}
320-
321-
if (__DEV__) {
322-
const isModernRoot =
323-
isContainerMarkedAsRoot(container) &&
324-
container._reactRootContainer === undefined;
325-
if (isModernRoot) {
321+
} else {
322+
if (__DEV__) {
326323
console.error(
327-
'You are calling ReactDOM.render() on a container that was previously ' +
328-
'passed to ReactDOMClient.createRoot(). This is not supported. ' +
329-
'Did you mean to call root.render(element)?',
324+
'ReactDOM.render is no longer supported in React 18. Use createRoot ' +
325+
'instead. Until you switch to the new API, your app will behave as ' +
326+
"if it's running React 17. Learn " +
327+
'more: https://reactjs.org/link/switch-to-createroot',
330328
);
331329
}
330+
331+
if (!isValidContainerLegacy(container)) {
332+
throw new Error('Target container is not a DOM element.');
333+
}
334+
335+
if (__DEV__) {
336+
const isModernRoot =
337+
isContainerMarkedAsRoot(container) &&
338+
container._reactRootContainer === undefined;
339+
if (isModernRoot) {
340+
console.error(
341+
'You are calling ReactDOM.render() on a container that was previously ' +
342+
'passed to ReactDOMClient.createRoot(). This is not supported. ' +
343+
'Did you mean to call root.render(element)?',
344+
);
345+
}
346+
}
347+
return legacyRenderSubtreeIntoContainer(
348+
null,
349+
element,
350+
container,
351+
false,
352+
callback,
353+
);
332354
}
333-
return legacyRenderSubtreeIntoContainer(
334-
null,
335-
element,
336-
container,
337-
false,
338-
callback,
339-
);
340355
}
341356

342357
export function unstable_renderSubtreeIntoContainer(
@@ -345,100 +360,116 @@ export function unstable_renderSubtreeIntoContainer(
345360
containerNode: Container,
346361
callback: ?Function,
347362
): React$Component<any, any> | PublicInstance | null {
348-
if (__DEV__) {
349-
console.error(
350-
'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported ' +
351-
'in React 18. Consider using a portal instead. Until you switch to ' +
352-
"the createRoot API, your app will behave as if it's running React " +
353-
'17. Learn more: https://reactjs.org/link/switch-to-createroot',
363+
if (disableLegacyReactDOMRenderAPIs) {
364+
throw new Error(
365+
'ReactDOM.unstable_renderSubtreeIntoContainer is no longer supported. ' +
366+
'Use a portal instead. Learn more: ' +
367+
'https://reactjs.org/link/switch-to-createroot',
354368
);
355-
}
369+
} else {
370+
if (__DEV__) {
371+
console.error(
372+
'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported ' +
373+
'in React 18. Consider using a portal instead. Until you switch to ' +
374+
"the createRoot API, your app will behave as if it's running React " +
375+
'17. Learn more: https://reactjs.org/link/switch-to-createroot',
376+
);
377+
}
356378

357-
if (!isValidContainerLegacy(containerNode)) {
358-
throw new Error('Target container is not a DOM element.');
359-
}
379+
if (!isValidContainerLegacy(containerNode)) {
380+
throw new Error('Target container is not a DOM element.');
381+
}
360382

361-
if (parentComponent == null || !hasInstance(parentComponent)) {
362-
throw new Error('parentComponent must be a valid React Component');
363-
}
383+
if (parentComponent == null || !hasInstance(parentComponent)) {
384+
throw new Error('parentComponent must be a valid React Component');
385+
}
364386

365-
return legacyRenderSubtreeIntoContainer(
366-
parentComponent,
367-
element,
368-
containerNode,
369-
false,
370-
callback,
371-
);
387+
return legacyRenderSubtreeIntoContainer(
388+
parentComponent,
389+
element,
390+
containerNode,
391+
false,
392+
callback,
393+
);
394+
}
372395
}
373396

374397
export function unmountComponentAtNode(container: Container): boolean {
375-
if (!isValidContainerLegacy(container)) {
398+
if (disableLegacyReactDOMRenderAPIs) {
376399
throw new Error(
377-
'unmountComponentAtNode(...): Target container is not a DOM element.',
400+
'ReactDOM.unmountComponentAtNode is no longer supported. Did you mean ' +
401+
'to call root.unmount()? Learn more: ' +
402+
'https://reactjs.org/link/switch-to-createroot',
378403
);
379-
}
380-
381-
if (__DEV__) {
382-
const isModernRoot =
383-
isContainerMarkedAsRoot(container) &&
384-
container._reactRootContainer === undefined;
385-
if (isModernRoot) {
386-
console.error(
387-
'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
388-
'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?',
404+
} else {
405+
if (!isValidContainerLegacy(container)) {
406+
throw new Error(
407+
'unmountComponentAtNode(...): Target container is not a DOM element.',
389408
);
390409
}
391-
}
392410

393-
if (container._reactRootContainer) {
394411
if (__DEV__) {
395-
const rootEl = getReactRootElementInContainer(container);
396-
const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
397-
if (renderedByDifferentReact) {
412+
const isModernRoot =
413+
isContainerMarkedAsRoot(container) &&
414+
container._reactRootContainer === undefined;
415+
if (isModernRoot) {
398416
console.error(
399-
"unmountComponentAtNode(): The node you're attempting to unmount " +
400-
'was rendered by another copy of React.',
417+
'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
418+
'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?',
401419
);
402420
}
403421
}
404422

405-
// Unmount should not be batched.
406-
flushSync(() => {
407-
legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
408-
// $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
409-
container._reactRootContainer = null;
410-
unmarkContainerAsRoot(container);
423+
if (container._reactRootContainer) {
424+
if (__DEV__) {
425+
const rootEl = getReactRootElementInContainer(container);
426+
const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
427+
if (renderedByDifferentReact) {
428+
console.error(
429+
"unmountComponentAtNode(): The node you're attempting to unmount " +
430+
'was rendered by another copy of React.',
431+
);
432+
}
433+
}
434+
435+
// Unmount should not be batched.
436+
flushSync(() => {
437+
legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
438+
// $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
439+
container._reactRootContainer = null;
440+
unmarkContainerAsRoot(container);
441+
});
411442
});
412-
});
413-
// If you call unmountComponentAtNode twice in quick succession, you'll
414-
// get `true` twice. That's probably fine?
415-
return true;
416-
} else {
417-
if (__DEV__) {
418-
const rootEl = getReactRootElementInContainer(container);
419-
const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
420-
421-
// Check if the container itself is a React root node.
422-
const isContainerReactRoot =
423-
container.nodeType === ELEMENT_NODE &&
424-
isValidContainerLegacy(container.parentNode) &&
425-
// $FlowFixMe[prop-missing]
426-
// $FlowFixMe[incompatible-use]
427-
!!container.parentNode._reactRootContainer;
428-
429-
if (hasNonRootReactChild) {
430-
console.error(
431-
"unmountComponentAtNode(): The node you're attempting to unmount " +
432-
'was rendered by React and is not a top-level container. %s',
433-
isContainerReactRoot
434-
? 'You may have accidentally passed in a React root node instead ' +
435-
'of its container.'
436-
: 'Instead, have the parent component update its state and ' +
437-
'rerender in order to remove this component.',
438-
);
443+
// If you call unmountComponentAtNode twice in quick succession, you'll
444+
// get `true` twice. That's probably fine?
445+
return true;
446+
} else {
447+
if (__DEV__) {
448+
const rootEl = getReactRootElementInContainer(container);
449+
const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
450+
451+
// Check if the container itself is a React root node.
452+
const isContainerReactRoot =
453+
container.nodeType === ELEMENT_NODE &&
454+
isValidContainerLegacy(container.parentNode) &&
455+
// $FlowFixMe[prop-missing]
456+
// $FlowFixMe[incompatible-use]
457+
!!container.parentNode._reactRootContainer;
458+
459+
if (hasNonRootReactChild) {
460+
console.error(
461+
"unmountComponentAtNode(): The node you're attempting to unmount " +
462+
'was rendered by React and is not a top-level container. %s',
463+
isContainerReactRoot
464+
? 'You may have accidentally passed in a React root node instead ' +
465+
'of its container.'
466+
: 'Instead, have the parent component update its state and ' +
467+
'rerender in order to remove this component.',
468+
);
469+
}
439470
}
440-
}
441471

442-
return false;
472+
return false;
473+
}
443474
}
444475
}

packages/shared/ReactFeatureFlags.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ export const enableCustomElementPropertySupport = __EXPERIMENTAL__;
199199
// Disables children for <textarea> elements
200200
export const disableTextareaChildren = false;
201201

202+
// Removes legacy render API from react-dom, including render, hydrate, hydrate
203+
// and unmountComponentAtNode.
204+
export const disableLegacyReactDOMRenderAPIs = true;
205+
202206
// -----------------------------------------------------------------------------
203207
// Debugging and DevTools
204208
// -----------------------------------------------------------------------------

packages/shared/forks/ReactFeatureFlags.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const enableSuspenseCallback = false;
5656
export const disableLegacyContext = false;
5757
export const enableTrustedTypesIntegration = false;
5858
export const disableTextareaChildren = false;
59+
export const disableLegacyReactDOMRenderAPIs = true;
5960
export const enableSuspenseAvoidThisFallback = false;
6061
export const enableSuspenseAvoidThisFallbackFizz = false;
6162
export const enableCPUSuspense = true;

packages/shared/forks/ReactFeatureFlags.native-oss.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const enableSuspenseCallback = false;
3838
export const disableLegacyContext = false;
3939
export const enableTrustedTypesIntegration = false;
4040
export const disableTextareaChildren = false;
41+
export const disableLegacyReactDOMRenderAPIs = true;
4142
export const disableModulePatternComponents = false;
4243
export const enableSuspenseAvoidThisFallback = false;
4344
export const enableSuspenseAvoidThisFallbackFizz = false;

0 commit comments

Comments
 (0)