From 732f992e6fa97f645a9ac1e24a73bd5b1364e735 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Fri, 17 Nov 2023 15:38:13 -0800 Subject: [PATCH] React has deprecated module pattern Function Components for many years at this point. Supporting this pattern required React to have a concept of an indeterminate component so that when a component first renders it can turn into either a ClassComponent or a FunctionComponent depending on what it returns. While this feature was deprecated and put behind a flag it is still in stable. This change remvoes the flag, removes the warnings, and removes the concept of IndeterminateComponent from the React codebase. --- .../src/backend/renderer.js | 2 +- .../__tests__/ReactComponentLifeCycle-test.js | 68 ----- .../__tests__/ReactCompositeComponent-test.js | 74 ++--- .../ReactCompositeComponentState-test.js | 66 ---- .../ReactDOMServerIntegrationElements-test.js | 27 +- .../ReactErrorBoundaries-test.internal.js | 50 --- ...eactLegacyErrorBoundaries-test.internal.js | 48 --- packages/react-dom/src/__tests__/refs-test.js | 35 --- packages/react-reconciler/src/ReactFiber.js | 21 +- .../src/ReactFiberBeginWork.js | 287 ++++-------------- .../src/ReactFiberClassComponent.js | 19 +- .../src/ReactFiberCompleteWork.js | 2 - .../src/ReactFiberComponentStack.js | 2 - .../src/ReactFiberHydrationDiffs.js | 2 - .../src/ReactFiberWorkLoop.js | 8 - .../react-reconciler/src/ReactWorkTags.js | 1 - .../src/__tests__/ReactHooks-test.internal.js | 50 --- .../ReactHooksWithNoopRenderer-test.js | 38 --- .../src/__tests__/ReactIncremental-test.js | 42 --- ...tIncrementalErrorHandling-test.internal.js | 39 --- .../ReactSubtreeFlagsWarning-test.js | 8 +- .../src/getComponentNameFromFiber.js | 2 - .../__tests__/ReactFreshIntegration-test.js | 48 --- packages/react-server/src/ReactFizzServer.js | 99 ++---- packages/shared/ReactFeatureFlags.js | 2 - .../forks/ReactFeatureFlags.native-fb.js | 1 - .../forks/ReactFeatureFlags.native-oss.js | 1 - .../forks/ReactFeatureFlags.test-renderer.js | 1 - .../ReactFeatureFlags.test-renderer.native.js | 1 - .../ReactFeatureFlags.test-renderer.www.js | 1 - .../shared/forks/ReactFeatureFlags.www.js | 2 - yarn.lock | 3 +- 32 files changed, 126 insertions(+), 924 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index b1e5dac527e61..58a6717615492 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -225,7 +225,7 @@ export function getInternalReactConstants(version: string): { HostSingleton: 27, // Same as above HostText: 6, IncompleteClassComponent: 17, - IndeterminateComponent: 2, + IndeterminateComponent: 2, // removed in 19.0.0 LazyComponent: 16, LegacyHiddenComponent: 23, MemoComponent: 14, diff --git a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js index d07ab9e71b0b3..f89e95bcf5dde 100644 --- a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js +++ b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js @@ -14,7 +14,6 @@ let act; let React; let ReactDOM; let ReactDOMClient; -let PropTypes; let findDOMNode; const clone = function (o) { @@ -99,7 +98,6 @@ describe('ReactComponentLifeCycle', () => { findDOMNode = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode; ReactDOMClient = require('react-dom/client'); - PropTypes = require('prop-types'); }); it('should not reuse an instance when it has been unmounted', async () => { @@ -1114,72 +1112,6 @@ describe('ReactComponentLifeCycle', () => { }); }); - if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) { - // @gate !disableLegacyContext - it('calls effects on module-pattern component', async () => { - const log = []; - - function Parent() { - return { - render() { - expect(typeof this.props).toBe('object'); - log.push('render'); - return ; - }, - UNSAFE_componentWillMount() { - log.push('will mount'); - }, - componentDidMount() { - log.push('did mount'); - }, - componentDidUpdate() { - log.push('did update'); - }, - getChildContext() { - return {x: 2}; - }, - }; - } - Parent.childContextTypes = { - x: PropTypes.number, - }; - function Child(props, context) { - expect(context.x).toBe(2); - return
; - } - Child.contextTypes = { - x: PropTypes.number, - }; - - const root = ReactDOMClient.createRoot(document.createElement('div')); - await expect(async () => { - await act(() => { - root.render( c && log.push('ref')} />); - }); - }).toErrorDev( - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Parent to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Parent.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ); - await act(() => { - root.render( c && log.push('ref')} />); - }); - - expect(log).toEqual([ - 'will mount', - 'render', - 'did mount', - 'ref', - - 'render', - 'did update', - 'ref', - ]); - }); - } - it('should warn if getDerivedStateFromProps returns undefined', async () => { class MyComponent extends React.Component { state = {}; diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js index 561928b24faf3..2e56a911a0c38 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js @@ -211,63 +211,27 @@ describe('ReactCompositeComponent', () => { }); }); - if (require('shared/ReactFeatureFlags').disableModulePatternComponents) { - it('should not support module pattern components', async () => { - function Child({test}) { - return { - render() { - return
{test}
; - }, - }; - } - - const el = document.createElement('div'); - const root = ReactDOMClient.createRoot(el); - await expect(async () => { - await expect(async () => { - await act(() => { - root.render(); - }); - }).rejects.toThrow( - 'Objects are not valid as a React child (found: object with keys {render}).', - ); - }).toErrorDev( - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Child to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Child.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ); - - expect(el.textContent).toBe(''); - }); - } else { - it('should support module pattern components', () => { - function Child({test}) { - return { - render() { - return
{test}
; - }, - }; - } + it('should not support module pattern components', async () => { + function Child({test}) { + return { + render() { + return
{test}
; + }, + }; + } - const el = document.createElement('div'); - const root = ReactDOMClient.createRoot(el); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Child to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Child.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ); + const el = document.createElement('div'); + const root = ReactDOMClient.createRoot(el); + await expect(async () => { + await act(() => { + root.render(); + }); + }).rejects.toThrow( + 'Objects are not valid as a React child (found: object with keys {render}).', + ); - expect(el.textContent).toBe('test'); - }); - } + expect(el.textContent).toBe(''); + }); it('should use default values for undefined props', async () => { class Component extends React.Component { diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js index a1d3d28533fe9..ecb30f0f1d78e 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js @@ -527,72 +527,6 @@ describe('ReactCompositeComponent-state', () => { ]); }); - if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) { - it('should support stateful module pattern components', async () => { - function Child() { - return { - state: { - count: 123, - }, - render() { - return
{`count:${this.state.count}`}
; - }, - }; - } - - const el = document.createElement('div'); - const root = ReactDOMClient.createRoot(el); - expect(() => { - ReactDOM.flushSync(() => { - root.render(); - }); - }).toErrorDev( - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Child to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Child.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ); - - expect(el.textContent).toBe('count:123'); - }); - - it('should support getDerivedStateFromProps for module pattern components', async () => { - function Child() { - return { - state: { - count: 1, - }, - render() { - return
{`count:${this.state.count}`}
; - }, - }; - } - Child.getDerivedStateFromProps = (props, prevState) => { - return { - count: prevState.count + props.incrementBy, - }; - }; - - const el = document.createElement('div'); - const root = ReactDOMClient.createRoot(el); - await act(() => { - root.render(); - }); - - expect(el.textContent).toBe('count:1'); - await act(() => { - root.render(); - }); - expect(el.textContent).toBe('count:3'); - - await act(() => { - root.render(); - }); - expect(el.textContent).toBe('count:4'); - }); - } - it('should not support setState in componentWillUnmount', async () => { let subscription; class A extends React.Component { diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js index a66cd12cd9178..f492aebb455db 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js @@ -627,23 +627,9 @@ describe('ReactDOMServerIntegration', () => { checkFooDiv(await render()); }); - if (require('shared/ReactFeatureFlags').disableModulePatternComponents) { - itThrowsWhenRendering( - 'factory components', - async render => { - const FactoryComponent = () => { - return { - render: function () { - return
foo
; - }, - }; - }; - await render(, 1); - }, - 'Objects are not valid as a React child (found: object with keys {render})', - ); - } else { - itRenders('factory components', async render => { + itThrowsWhenRendering( + 'factory components', + async render => { const FactoryComponent = () => { return { render: function () { @@ -651,9 +637,10 @@ describe('ReactDOMServerIntegration', () => { }, }; }; - checkFooDiv(await render(, 1)); - }); - } + await render(, 1); + }, + 'Objects are not valid as a React child (found: object with keys {render})', + ); }); describe('component hierarchies', function () { diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index 36a227c0fabab..ffa923de3de58 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -879,56 +879,6 @@ describe('ReactErrorBoundaries', () => { expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); }); - // @gate !disableModulePatternComponents - it('renders an error state if module-style context provider throws in componentWillMount', async () => { - function BrokenComponentWillMountWithContext() { - return { - getChildContext() { - return {foo: 42}; - }, - render() { - return
{this.props.children}
; - }, - UNSAFE_componentWillMount() { - throw new Error('Hello'); - }, - }; - } - BrokenComponentWillMountWithContext.childContextTypes = { - foo: PropTypes.number, - }; - - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - - await expect(async () => { - await act(() => { - root.render( - - - , - ); - }); - }).toErrorDev([ - 'Warning: The component appears to be a function component that ' + - 'returns a class instance. ' + - 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ...gate(flags => - flags.disableLegacyContext - ? [ - 'Warning: BrokenComponentWillMountWithContext uses the legacy childContextTypes API which was removed in React 19. Use React.createContext() instead.', - 'Warning: BrokenComponentWillMountWithContext uses the legacy childContextTypes API which was removed in React 19. Use React.createContext() instead.', - ] - : [], - ), - ]); - - expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - }); - it('mounts the error message if mounting fails', async () => { function renderError(error) { return ; diff --git a/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js index 8c53de16bf814..b0b223dd43bee 100644 --- a/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js @@ -849,54 +849,6 @@ describe('ReactLegacyErrorBoundaries', () => { expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); }); - if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) { - // @gate !disableLegacyMode - it('renders an error state if module-style context provider throws in componentWillMount', () => { - function BrokenComponentWillMountWithContext() { - return { - getChildContext() { - return {foo: 42}; - }, - render() { - return
{this.props.children}
; - }, - UNSAFE_componentWillMount() { - throw new Error('Hello'); - }, - }; - } - BrokenComponentWillMountWithContext.childContextTypes = { - foo: PropTypes.number, - }; - - const container = document.createElement('div'); - expect(() => - ReactDOM.render( - - - , - container, - ), - ).toErrorDev([ - 'Warning: The component appears to be a function component that ' + - 'returns a class instance. ' + - 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ...gate(flags => - flags.disableLegacyContext - ? [ - 'Warning: BrokenComponentWillMountWithContext uses the legacy childContextTypes API which was removed in React 19. Use React.createContext() instead.', - 'Warning: BrokenComponentWillMountWithContext uses the legacy childContextTypes API which was removed in React 19. Use React.createContext() instead.', - ] - : [], - ), - ]); - expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - }); - } - // @gate !disableLegacyMode it('mounts the error message if mounting fails', () => { function renderError(error) { diff --git a/packages/react-dom/src/__tests__/refs-test.js b/packages/react-dom/src/__tests__/refs-test.js index f43ada19e004f..4a638ef17c566 100644 --- a/packages/react-dom/src/__tests__/refs-test.js +++ b/packages/react-dom/src/__tests__/refs-test.js @@ -11,7 +11,6 @@ let React = require('react'); let ReactDOMClient = require('react-dom/client'); -let ReactFeatureFlags = require('shared/ReactFeatureFlags'); let act = require('internal-test-utils').act; // This is testing if string refs are deleted from `instance.refs` @@ -24,7 +23,6 @@ describe('reactiverefs', () => { jest.resetModules(); React = require('react'); ReactDOMClient = require('react-dom/client'); - ReactFeatureFlags = require('shared/ReactFeatureFlags'); act = require('internal-test-utils').act; }); @@ -195,38 +193,6 @@ describe('reactiverefs', () => { }); }); -if (!ReactFeatureFlags.disableModulePatternComponents) { - describe('factory components', () => { - it('Should correctly get the ref', async () => { - function Comp() { - return { - elemRef: React.createRef(), - render() { - return
; - }, - }; - } - - let inst; - await expect(async () => { - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - - await act(() => { - root.render( (inst = current)} />); - }); - }).toErrorDev( - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Comp to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Comp.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ); - expect(inst.elemRef.current.tagName).toBe('DIV'); - }); - }); -} - /** * Tests that when a ref hops around children, we can track that correctly. */ @@ -236,7 +202,6 @@ describe('ref swapping', () => { jest.resetModules(); React = require('react'); ReactDOMClient = require('react-dom/client'); - ReactFeatureFlags = require('shared/ReactFeatureFlags'); act = require('internal-test-utils').act; RefHopsAround = class extends React.Component { diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index ce909b802530a..dbc2b42a7ea85 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -42,7 +42,6 @@ import { import {NoFlags, Placement, StaticMask} from './ReactFiberFlags'; import {ConcurrentRoot} from './ReactRootTags'; import { - IndeterminateComponent, ClassComponent, HostRoot, HostComponent, @@ -248,19 +247,10 @@ export function isSimpleFunctionComponent(type: any): boolean { ); } -export function resolveLazyComponentTag(Component: Function): WorkTag { - if (typeof Component === 'function') { - return shouldConstruct(Component) ? ClassComponent : FunctionComponent; - } else if (Component !== undefined && Component !== null) { - const $$typeof = Component.$$typeof; - if ($$typeof === REACT_FORWARD_REF_TYPE) { - return ForwardRef; - } - if ($$typeof === REACT_MEMO_TYPE) { - return MemoComponent; - } - } - return IndeterminateComponent; +export function isFunctionClassComponent( + type: (...args: Array) => mixed, +): boolean { + return shouldConstruct(type); } // This is used to create an alternate fiber to do work on. @@ -351,7 +341,6 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { workInProgress._debugInfo = current._debugInfo; workInProgress._debugNeedsRemount = current._debugNeedsRemount; switch (workInProgress.tag) { - case IndeterminateComponent: case FunctionComponent: case SimpleMemoComponent: workInProgress.type = resolveFunctionForHotReloading(current.type); @@ -492,7 +481,7 @@ export function createFiberFromTypeAndProps( mode: TypeOfMode, lanes: Lanes, ): Fiber { - let fiberTag = IndeterminateComponent; + let fiberTag = FunctionComponent; // The resolved type is set if we know what the final type will be. I.e. it's not lazy. let resolvedType = type; if (typeof type === 'function') { diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index c53e01d4ec2c6..998d18ea03365 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -46,7 +46,6 @@ import { setIsStrictModeForDevtools, } from './ReactFiberDevToolsHook'; import { - IndeterminateComponent, FunctionComponent, ClassComponent, HostRoot, @@ -95,7 +94,6 @@ import ReactSharedInternals from 'shared/ReactSharedInternals'; import { debugRenderPhaseSideEffectsForStrictMode, disableLegacyContext, - disableModulePatternComponents, enableProfilerCommitHooks, enableProfilerTimer, enableScopeAPI, @@ -115,7 +113,12 @@ import shallowEqual from 'shared/shallowEqual'; import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber'; import getComponentNameFromType from 'shared/getComponentNameFromType'; import ReactStrictModeWarnings from './ReactStrictModeWarnings'; -import {REACT_LAZY_TYPE, getIteratorFn} from 'shared/ReactSymbols'; +import { + REACT_LAZY_TYPE, + REACT_FORWARD_REF_TYPE, + REACT_MEMO_TYPE, + getIteratorFn, +} from 'shared/ReactSymbols'; import { getCurrentFiberOwnerNameInDevOrNull, setIsRendering, @@ -236,7 +239,6 @@ import { queueHydrationError, } from './ReactFiberHydrationContext'; import { - adoptClassInstance, constructClassInstance, mountClassInstance, resumeMountClassInstance, @@ -244,12 +246,12 @@ import { } from './ReactFiberClassComponent'; import {resolveDefaultProps} from './ReactFiberLazyComponent'; import { - resolveLazyComponentTag, createFiberFromTypeAndProps, createFiberFromFragment, createFiberFromOffscreen, createWorkInProgress, isSimpleFunctionComponent, + isFunctionClassComponent, } from './ReactFiber'; import { retryDehydratedSuspenseBoundary, @@ -305,7 +307,6 @@ export const SelectiveHydrationException: mixed = new Error( let didReceiveUpdate: boolean = false; let didWarnAboutBadClass; -let didWarnAboutModulePatternComponent; let didWarnAboutContextTypeOnFunctionComponent; let didWarnAboutGetDerivedStateOnFunctionComponent; let didWarnAboutFunctionRefs; @@ -316,7 +317,6 @@ let didWarnAboutDefaultPropsOnFunctionComponent; if (__DEV__) { didWarnAboutBadClass = ({}: {[string]: boolean}); - didWarnAboutModulePatternComponent = ({}: {[string]: boolean}); didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutFunctionRefs = ({}: {[string]: boolean}); @@ -1053,6 +1053,43 @@ function updateFunctionComponent( nextProps: any, renderLanes: Lanes, ) { + if (__DEV__) { + if ( + Component.prototype && + typeof Component.prototype.render === 'function' + ) { + const componentName = getComponentNameFromType(Component) || 'Unknown'; + + if (!didWarnAboutBadClass[componentName]) { + console.error( + "The <%s /> component appears to have a render method, but doesn't extend React.Component. " + + 'This is likely to cause errors. Change %s to extend React.Component instead.', + componentName, + componentName, + ); + didWarnAboutBadClass[componentName] = true; + } + } + + if (workInProgress.mode & StrictLegacyMode) { + ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null); + } + + if (current === null) { + // Some validations were previously done in mountIndeterminateComponent however and are now run + // in updateFuntionComponent but only on mount + validateFunctionComponentInDev(workInProgress, workInProgress.type); + + if (disableLegacyContext && Component.contextTypes) { + console.error( + '%s uses the legacy contextTypes API which is no longer supported. ' + + 'Use React.createContext() with React.useContext() instead.', + getComponentNameFromType(Component) || 'Unknown', + ); + } + } + } + let context; if (!disableLegacyContext) { const unmaskedContext = getUnmaskedContext(workInProgress, Component, true); @@ -1699,64 +1736,64 @@ function mountLazyComponent( let Component = init(payload); // Store the unwrapped component in the type. workInProgress.type = Component; - const resolvedTag = (workInProgress.tag = resolveLazyComponentTag(Component)); + const resolvedProps = resolveDefaultProps(Component, props); - let child; - switch (resolvedTag) { - case FunctionComponent: { + if (typeof Component === 'function') { + if (isFunctionClassComponent(Component)) { + workInProgress.tag = ClassComponent; if (__DEV__) { - validateFunctionComponentInDev(workInProgress, Component); workInProgress.type = Component = - resolveFunctionForHotReloading(Component); + resolveClassForHotReloading(Component); } - child = updateFunctionComponent( + return updateClassComponent( null, workInProgress, Component, resolvedProps, renderLanes, ); - return child; - } - case ClassComponent: { + } else { + workInProgress.tag = FunctionComponent; if (__DEV__) { + validateFunctionComponentInDev(workInProgress, Component); workInProgress.type = Component = - resolveClassForHotReloading(Component); + resolveFunctionForHotReloading(Component); } - child = updateClassComponent( + return updateFunctionComponent( null, workInProgress, Component, resolvedProps, renderLanes, ); - return child; } - case ForwardRef: { + } else if (Component !== undefined && Component !== null) { + const $$typeof = Component.$$typeof; + if ($$typeof === REACT_FORWARD_REF_TYPE) { + workInProgress.tag = ForwardRef; if (__DEV__) { workInProgress.type = Component = resolveForwardRefForHotReloading(Component); } - child = updateForwardRef( + return updateForwardRef( null, workInProgress, Component, resolvedProps, renderLanes, ); - return child; - } - case MemoComponent: { - child = updateMemoComponent( + } else if ($$typeof === REACT_MEMO_TYPE) { + workInProgress.tag = MemoComponent; + return updateMemoComponent( null, workInProgress, Component, resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too renderLanes, ); - return child; } } + let hint = ''; if (__DEV__) { if ( @@ -1816,194 +1853,6 @@ function mountIncompleteClassComponent( ); } -function mountIndeterminateComponent( - _current: null | Fiber, - workInProgress: Fiber, - Component: $FlowFixMe, - renderLanes: Lanes, -) { - resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress); - - const props = workInProgress.pendingProps; - let context; - if (!disableLegacyContext) { - const unmaskedContext = getUnmaskedContext( - workInProgress, - Component, - false, - ); - context = getMaskedContext(workInProgress, unmaskedContext); - } - - prepareToReadContext(workInProgress, renderLanes); - let value; - let hasId; - - if (enableSchedulingProfiler) { - markComponentRenderStarted(workInProgress); - } - if (__DEV__) { - if ( - Component.prototype && - typeof Component.prototype.render === 'function' - ) { - const componentName = getComponentNameFromType(Component) || 'Unknown'; - - if (!didWarnAboutBadClass[componentName]) { - console.error( - "The <%s /> component appears to have a render method, but doesn't extend React.Component. " + - 'This is likely to cause errors. Change %s to extend React.Component instead.', - componentName, - componentName, - ); - didWarnAboutBadClass[componentName] = true; - } - } - - if (workInProgress.mode & StrictLegacyMode) { - ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null); - } - - setIsRendering(true); - ReactCurrentOwner.current = workInProgress; - value = renderWithHooks( - null, - workInProgress, - Component, - props, - context, - renderLanes, - ); - hasId = checkDidRenderIdHook(); - setIsRendering(false); - } else { - value = renderWithHooks( - null, - workInProgress, - Component, - props, - context, - renderLanes, - ); - hasId = checkDidRenderIdHook(); - } - if (enableSchedulingProfiler) { - markComponentRenderStopped(); - } - - // React DevTools reads this flag. - workInProgress.flags |= PerformedWork; - - if (__DEV__) { - // Support for module components is deprecated and is removed behind a flag. - // Whether or not it would crash later, we want to show a good message in DEV first. - if ( - typeof value === 'object' && - value !== null && - typeof value.render === 'function' && - value.$$typeof === undefined - ) { - const componentName = getComponentNameFromType(Component) || 'Unknown'; - if (!didWarnAboutModulePatternComponent[componentName]) { - console.error( - 'The <%s /> component appears to be a function component that returns a class instance. ' + - 'Change %s to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " + - 'cannot be called with `new` by React.', - componentName, - componentName, - componentName, - ); - didWarnAboutModulePatternComponent[componentName] = true; - } - } - } - - if ( - // Run these checks in production only if the flag is off. - // Eventually we'll delete this branch altogether. - !disableModulePatternComponents && - typeof value === 'object' && - value !== null && - typeof value.render === 'function' && - value.$$typeof === undefined - ) { - if (__DEV__) { - const componentName = getComponentNameFromType(Component) || 'Unknown'; - if (!didWarnAboutModulePatternComponent[componentName]) { - console.error( - 'The <%s /> component appears to be a function component that returns a class instance. ' + - 'Change %s to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " + - 'cannot be called with `new` by React.', - componentName, - componentName, - componentName, - ); - didWarnAboutModulePatternComponent[componentName] = true; - } - } - - // Proceed under the assumption that this is a class instance - workInProgress.tag = ClassComponent; - - // Throw out any hooks that were used. - workInProgress.memoizedState = null; - workInProgress.updateQueue = null; - - // Push context providers early to prevent context stack mismatches. - // During mounting we don't know the child context yet as the instance doesn't exist. - // We will invalidate the child context in finishClassComponent() right after rendering. - let hasContext = false; - if (isLegacyContextProvider(Component)) { - hasContext = true; - pushLegacyContextProvider(workInProgress); - } else { - hasContext = false; - } - - workInProgress.memoizedState = - value.state !== null && value.state !== undefined ? value.state : null; - - initializeUpdateQueue(workInProgress); - - adoptClassInstance(workInProgress, value); - mountClassInstance(workInProgress, Component, props, renderLanes); - return finishClassComponent( - null, - workInProgress, - Component, - true, - hasContext, - renderLanes, - ); - } else { - // Proceed under the assumption that this is a function component - workInProgress.tag = FunctionComponent; - if (__DEV__) { - if (disableLegacyContext && Component.contextTypes) { - console.error( - '%s uses the legacy contextTypes API which was removed in React 19. ' + - 'Use React.createContext() with React.useContext() instead.', - getComponentNameFromType(Component) || 'Unknown', - ); - } - } - - if (getIsHydrating() && hasId) { - pushMaterializedTreeId(workInProgress); - } - - reconcileChildren(null, workInProgress, value, renderLanes); - if (__DEV__) { - validateFunctionComponentInDev(workInProgress, Component); - } - return workInProgress.child; - } -} - function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) { if (__DEV__) { if (Component) { @@ -4027,14 +3876,6 @@ function beginWork( workInProgress.lanes = NoLanes; switch (workInProgress.tag) { - case IndeterminateComponent: { - return mountIndeterminateComponent( - current, - workInProgress, - workInProgress.type, - renderLanes, - ); - } case LazyComponent: { const elementType = workInProgress.elementType; return mountLazyComponent( diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index 231e6a3508e4e..47d1c3cfee476 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -569,16 +569,6 @@ function checkClassInstance(workInProgress: Fiber, ctor: any, newProps: any) { } } -function adoptClassInstance(workInProgress: Fiber, instance: any): void { - instance.updater = classComponentUpdater; - workInProgress.stateNode = instance; - // The instance needs access to the fiber so that it can schedule updates - setInstance(instance, workInProgress); - if (__DEV__) { - instance._reactInternalInstance = fakeInternalInstance; - } -} - function constructClassInstance( workInProgress: Fiber, ctor: any, @@ -659,7 +649,13 @@ function constructClassInstance( instance.state !== null && instance.state !== undefined ? instance.state : null); - adoptClassInstance(workInProgress, instance); + instance.updater = classComponentUpdater; + workInProgress.stateNode = instance; + // The instance needs access to the fiber so that it can schedule updates + setInstance(instance, workInProgress); + if (__DEV__) { + instance._reactInternalInstance = fakeInternalInstance; + } if (__DEV__) { if (typeof ctor.getDerivedStateFromProps === 'function' && state === null) { @@ -1230,7 +1226,6 @@ function updateClassInstance( } export { - adoptClassInstance, constructClassInstance, mountClassInstance, resumeMountClassInstance, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 89044182672ad..a07a9739016a9 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -45,7 +45,6 @@ import { import {now} from './Scheduler'; import { - IndeterminateComponent, FunctionComponent, ClassComponent, HostRoot, @@ -949,7 +948,6 @@ function completeWork( // for hydration. popTreeContext(workInProgress); switch (workInProgress.tag) { - case IndeterminateComponent: case LazyComponent: case SimpleMemoComponent: case FunctionComponent: diff --git a/packages/react-reconciler/src/ReactFiberComponentStack.js b/packages/react-reconciler/src/ReactFiberComponentStack.js index 36e22e8a9b1f2..f292cb51d10b4 100644 --- a/packages/react-reconciler/src/ReactFiberComponentStack.js +++ b/packages/react-reconciler/src/ReactFiberComponentStack.js @@ -17,7 +17,6 @@ import { SuspenseComponent, SuspenseListComponent, FunctionComponent, - IndeterminateComponent, ForwardRef, SimpleMemoComponent, ClassComponent, @@ -47,7 +46,6 @@ function describeFiber(fiber: Fiber): string { case SuspenseListComponent: return describeBuiltInComponentFrame('SuspenseList', owner); case FunctionComponent: - case IndeterminateComponent: case SimpleMemoComponent: return describeFunctionComponentFrame(fiber.type, owner); case ForwardRef: diff --git a/packages/react-reconciler/src/ReactFiberHydrationDiffs.js b/packages/react-reconciler/src/ReactFiberHydrationDiffs.js index 812d9d046a533..021da8abf33f1 100644 --- a/packages/react-reconciler/src/ReactFiberHydrationDiffs.js +++ b/packages/react-reconciler/src/ReactFiberHydrationDiffs.js @@ -17,7 +17,6 @@ import { SuspenseComponent, SuspenseListComponent, FunctionComponent, - IndeterminateComponent, ForwardRef, SimpleMemoComponent, ClassComponent, @@ -87,7 +86,6 @@ function describeFiberType(fiber: Fiber): null | string { case SuspenseListComponent: return 'SuspenseList'; case FunctionComponent: - case IndeterminateComponent: case SimpleMemoComponent: const fn = fiber.type; return fn.displayName || fn.name || null; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index fb0b710dbb81b..36a4179048ba1 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -89,7 +89,6 @@ import { } from './ReactTypeOfMode'; import { HostRoot, - IndeterminateComponent, ClassComponent, SuspenseComponent, SuspenseListComponent, @@ -2388,12 +2387,6 @@ function replaySuspendedUnitOfWork(unitOfWork: Fiber): void { startProfilerTimer(unitOfWork); } switch (unitOfWork.tag) { - case IndeterminateComponent: { - // Because it suspended with `use`, we can assume it's a - // function component. - unitOfWork.tag = FunctionComponent; - // Fallthrough to the next branch. - } case SimpleMemoComponent: case FunctionComponent: { // Resolve `defaultProps`. This logic is copied from `beginWork`. @@ -3816,7 +3809,6 @@ export function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber: Fiber) { const tag = fiber.tag; if ( - tag !== IndeterminateComponent && tag !== HostRoot && tag !== ClassComponent && tag !== FunctionComponent && diff --git a/packages/react-reconciler/src/ReactWorkTags.js b/packages/react-reconciler/src/ReactWorkTags.js index 8e928d671dc87..bc6782b02f610 100644 --- a/packages/react-reconciler/src/ReactWorkTags.js +++ b/packages/react-reconciler/src/ReactWorkTags.js @@ -39,7 +39,6 @@ export type WorkTag = export const FunctionComponent = 0; export const ClassComponent = 1; -export const IndeterminateComponent = 2; // Before we know whether it is function or class export const HostRoot = 3; // Root of a host tree. Could be nested inside another node. export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer. export const HostComponent = 5; diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index 5bfa66d66fab9..b63a8b23476e4 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -1308,16 +1308,6 @@ describe('ReactHooks', () => { return
; }); - function Factory() { - return { - state: {}, - render() { - renderCount++; - return
; - }, - }; - } - let renderer; await act(() => { renderer = ReactTestRenderer.create(null, {unstable_isConcurrent: true}); @@ -1410,46 +1400,6 @@ describe('ReactHooks', () => { }); expect(renderCount).toBe(__DEV__ ? 2 : 1); - if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) { - renderCount = 0; - await expect(async () => { - await act(() => { - renderer.update(); - }); - }).toErrorDev( - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Factory to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Factory.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ); - expect(renderCount).toBe(1); - renderCount = 0; - await act(() => { - renderer.update(); - }); - expect(renderCount).toBe(1); - - renderCount = 0; - await act(() => { - renderer.update( - - - , - ); - }); - expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class - renderCount = 0; - await act(() => { - renderer.update( - - - , - ); - }); - expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class - } - renderCount = 0; await act(() => { renderer.update(); diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index 45b223e8106a4..04e7be86c61cf 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -227,44 +227,6 @@ describe('ReactHooksWithNoopRenderer', () => { await waitForAll([10]); }); - // @gate !disableModulePatternComponents - it('throws inside module-style components', async () => { - function Counter() { - return { - render() { - const [count] = useState(0); - return ; - }, - }; - } - ReactNoop.render(); - await expect( - async () => - await waitForThrow( - 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen ' + - 'for one of the following reasons:\n' + - '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + - '2. You might be breaking the Rules of Hooks\n' + - '3. You might have more than one copy of React in the same app\n' + - 'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.', - ), - ).toErrorDev( - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Counter to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Counter.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ); - - // Confirm that a subsequent hook works properly. - function GoodCounter(props) { - const [count] = useState(props.initialCount); - return ; - } - ReactNoop.render(); - await waitForAll([10]); - }); - it('throws when called outside the render phase', async () => { expect(() => { expect(() => useState(0)).toThrow( diff --git a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js index 13f904bf9d014..4beb0a12dabb2 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js @@ -1864,48 +1864,6 @@ describe('ReactIncremental', () => { ]); }); - // @gate !disableModulePatternComponents - // @gate !disableLegacyContext - it('does not leak own context into context provider (factory components)', async () => { - function Recurse(props, context) { - return { - getChildContext() { - return {n: (context.n || 3) - 1}; - }, - render() { - Scheduler.log('Recurse ' + JSON.stringify(context)); - if (context.n === 0) { - return null; - } - return ; - }, - }; - } - Recurse.contextTypes = { - n: PropTypes.number, - }; - Recurse.childContextTypes = { - n: PropTypes.number, - }; - - ReactNoop.render(); - await expect( - async () => - await waitForAll([ - 'Recurse {}', - 'Recurse {"n":2}', - 'Recurse {"n":1}', - 'Recurse {"n":0}', - ]), - ).toErrorDev([ - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Recurse to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Recurse.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ]); - }); - // @gate www // @gate !disableLegacyContext it('provides context when reusing work', async () => { diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js index 6d86507d5bdec..c6e342871b198 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js @@ -1754,45 +1754,6 @@ describe('ReactIncrementalErrorHandling', () => { ); }); - // @gate !disableModulePatternComponents - it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', async () => { - function Provider() { - return { - getChildContext() { - return {foo: 'bar'}; - }, - render() { - return 'Hi'; - }, - }; - } - Provider.childContextTypes = { - x: () => {}, - }; - Provider.getDerivedStateFromProps = () => { - throw new Error('Oops!'); - }; - - ReactNoop.render(); - await expect(async () => { - await waitForThrow('Oops!'); - }).toErrorDev([ - 'Warning: The component appears to be a function component that returns a class instance. ' + - 'Change Provider to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - '`Provider.prototype = React.Component.prototype`. ' + - "Don't use an arrow function since it cannot be called with `new` by React.", - ...gate(flags => - flags.disableLegacyContext - ? [ - 'Warning: Provider uses the legacy childContextTypes API which was removed in React 19. Use React.createContext() instead.', - 'Warning: Provider uses the legacy childContextTypes API which was removed in React 19. Use React.createContext() instead.', - ] - : [], - ), - ]); - }); - it('uncaught errors should be discarded if the render is aborted', async () => { const root = ReactNoop.createRoot(); diff --git a/packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js b/packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js index 49bde67837cdf..e5d9c9c445dad 100644 --- a/packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js @@ -132,11 +132,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { // @gate experimental || www it('regression: false positive for legacy suspense', async () => { - // Wrapping in memo because regular function components go through the - // mountIndeterminateComponent path, which acts like there's no `current` - // fiber even though there is. `memo` is not indeterminate, so it goes - // through the update path. - const Child = React.memo(({text}) => { + const Child = ({text}) => { // If text hasn't resolved, this will throw and exit before the passive // static effect flag is added by the useEffect call below. readText(text); @@ -147,7 +143,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { Scheduler.log(text); return text; - }); + }; function App() { return ( diff --git a/packages/react-reconciler/src/getComponentNameFromFiber.js b/packages/react-reconciler/src/getComponentNameFromFiber.js index 1a8464835ce4f..9eb7fdf4f8907 100644 --- a/packages/react-reconciler/src/getComponentNameFromFiber.js +++ b/packages/react-reconciler/src/getComponentNameFromFiber.js @@ -18,7 +18,6 @@ import { import { FunctionComponent, ClassComponent, - IndeterminateComponent, HostRoot, HostPortal, HostComponent, @@ -128,7 +127,6 @@ export default function getComponentNameFromFiber(fiber: Fiber): string | null { case ClassComponent: case FunctionComponent: case IncompleteClassComponent: - case IndeterminateComponent: case MemoComponent: case SimpleMemoComponent: if (typeof type === 'function') { diff --git a/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js b/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js index ed8c56072a217..c1e5f308b2202 100644 --- a/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js +++ b/packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js @@ -1639,54 +1639,6 @@ describe('ReactFreshIntegration', () => { } }); - if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) { - it('remounts deprecated factory components', async () => { - if (__DEV__) { - await expect(async () => { - await render(` - function Parent() { - return { - render() { - return ; - } - }; - }; - - function Child({prop}) { - return

{prop}1

; - }; - - export default Parent; - `); - }).toErrorDev( - 'The component appears to be a function component ' + - 'that returns a class instance.', - ); - const el = container.firstChild; - expect(el.textContent).toBe('A1'); - await patch(` - function Parent() { - return { - render() { - return ; - } - }; - }; - - function Child({prop}) { - return

{prop}2

; - }; - - export default Parent; - `); - // Like classes, factory components always remount. - expect(container.firstChild).not.toBe(el); - const newEl = container.firstChild; - expect(newEl.textContent).toBe('B2'); - } - }); - } - describe('with inline requires', () => { beforeEach(() => { global.FakeModuleSystem = {}; diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index c66608414d900..4567a1e80d13b 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -137,7 +137,6 @@ import { import ReactSharedInternals from 'shared/ReactSharedInternals'; import { disableLegacyContext, - disableModulePatternComponents, enableBigIntSupport, enableScopeAPI, enableSuspenseAvoidThisFallbackFizz, @@ -1388,7 +1387,6 @@ function renderClassComponent( } const didWarnAboutBadClass: {[string]: boolean} = {}; -const didWarnAboutModulePatternComponent: {[string]: boolean} = {}; const didWarnAboutContextTypeOnFunctionComponent: {[string]: boolean} = {}; const didWarnAboutGetDerivedStateOnFunctionComponent: {[string]: boolean} = {}; let didWarnAboutReassigningProps = false; @@ -1396,9 +1394,7 @@ const didWarnAboutDefaultPropsOnFunctionComponent: {[string]: boolean} = {}; let didWarnAboutGenerators = false; let didWarnAboutMaps = false; -// This would typically be a function component but we still support module pattern -// components for some reason. -function renderIndeterminateComponent( +function renderFunctionComponent( request: Request, task: Task, keyPath: KeyNode, @@ -1444,83 +1440,26 @@ function renderIndeterminateComponent( const actionStateMatchingIndex = getActionStateMatchingIndex(); if (__DEV__) { - // Support for module components is deprecated and is removed behind a flag. - // Whether or not it would crash later, we want to show a good message in DEV first. - if ( - typeof value === 'object' && - value !== null && - typeof value.render === 'function' && - value.$$typeof === undefined - ) { - const componentName = getComponentNameFromType(Component) || 'Unknown'; - if (!didWarnAboutModulePatternComponent[componentName]) { - console.error( - 'The <%s /> component appears to be a function component that returns a class instance. ' + - 'Change %s to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " + - 'cannot be called with `new` by React.', - componentName, - componentName, - componentName, - ); - didWarnAboutModulePatternComponent[componentName] = true; - } + if (disableLegacyContext && Component.contextTypes) { + console.error( + '%s uses the legacy contextTypes API which was removed in React 19. ' + + 'Use React.createContext() with React.useContext() instead.', + getComponentNameFromType(Component) || 'Unknown', + ); } } - - if ( - // Run these checks in production only if the flag is off. - // Eventually we'll delete this branch altogether. - !disableModulePatternComponents && - typeof value === 'object' && - value !== null && - typeof value.render === 'function' && - value.$$typeof === undefined - ) { - if (__DEV__) { - const componentName = getComponentNameFromType(Component) || 'Unknown'; - if (!didWarnAboutModulePatternComponent[componentName]) { - console.error( - 'The <%s /> component appears to be a function component that returns a class instance. ' + - 'Change %s to a class that extends React.Component instead. ' + - "If you can't use a class try assigning the prototype on the function as a workaround. " + - "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " + - 'cannot be called with `new` by React.', - componentName, - componentName, - componentName, - ); - didWarnAboutModulePatternComponent[componentName] = true; - } - } - - mountClassInstance(value, Component, props, legacyContext); - finishClassComponent(request, task, keyPath, value, Component, props); - } else { - // Proceed under the assumption that this is a function component - if (__DEV__) { - if (disableLegacyContext && Component.contextTypes) { - console.error( - '%s uses the legacy contextTypes API which was removed in React 19. ' + - 'Use React.createContext() with React.useContext() instead.', - getComponentNameFromType(Component) || 'Unknown', - ); - } - } - if (__DEV__) { - validateFunctionComponentInDev(Component); - } - finishFunctionComponent( - request, - task, - keyPath, - value, - hasId, - actionStateCount, - actionStateMatchingIndex, - ); + if (__DEV__) { + validateFunctionComponentInDev(Component); } + finishFunctionComponent( + request, + task, + keyPath, + value, + hasId, + actionStateCount, + actionStateMatchingIndex, + ); task.componentStack = previousComponentStack; } @@ -1825,7 +1764,7 @@ function renderElement( renderClassComponent(request, task, keyPath, type, props); return; } else { - renderIndeterminateComponent(request, task, keyPath, type, props); + renderFunctionComponent(request, task, keyPath, type, props); return; } } diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 9747dd38f6691..3861ea5b6329c 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -206,8 +206,6 @@ export const enableRenderableContext = __NEXT_MAJOR__; // when we plan to enable them. // ----------------------------------------------------------------------------- -export const disableModulePatternComponents = __NEXT_MAJOR__; - export const enableUseRefAccessWarning = false; // Enables time slicing for updates that aren't wrapped in startTransition. diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index e51541b1bb93d..1c6180ae903ae 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -34,7 +34,6 @@ export const { } = dynamicFlags; // The rest of the flags are static for better dead code elimination. -export const disableModulePatternComponents = true; export const enableDebugTracing = false; export const enableAsyncDebugInfo = false; export const enableSchedulingProfiler = __PROFILE__; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index d447207b98e80..1c3a95b52c40b 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -31,7 +31,6 @@ export const enableDeferRootSchedulingToMicrotask = __TODO_NEXT_RN_MAJOR__; export const alwaysThrottleRetries = __TODO_NEXT_RN_MAJOR__; export const enableInfiniteRenderLoopDetection = __TODO_NEXT_RN_MAJOR__; export const enableComponentStackLocations = __TODO_NEXT_RN_MAJOR__; -export const disableModulePatternComponents = __TODO_NEXT_RN_MAJOR__; // ----------------------------------------------------------------------------- // These are ready to flip after the next React npm release (or RN switches to diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index bce10070683f6..d5b60e8203396 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -94,7 +94,6 @@ export const disableLegacyMode = __NEXT_MAJOR__; export const disableLegacyContext = __NEXT_MAJOR__; export const disableDOMTestUtils = __NEXT_MAJOR__; export const enableNewBooleanProps = __NEXT_MAJOR__; -export const disableModulePatternComponents = __NEXT_MAJOR__; export const enableRenderableContext = __NEXT_MAJOR__; export const enableReactTestRendererWarning = __NEXT_MAJOR__; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index b184d47d8fe3a..710eeb607ebfb 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -34,7 +34,6 @@ export const enableSuspenseCallback = false; export const disableLegacyContext = false; export const enableTrustedTypesIntegration = false; export const disableTextareaChildren = false; -export const disableModulePatternComponents = true; export const enableComponentStackLocations = false; export const enableLegacyFBSupport = false; export const enableFilterEmptyStringAttributesDOM = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 87b5e0302aea2..a4a3c138a218d 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -34,7 +34,6 @@ export const enableSuspenseCallback = true; export const disableLegacyContext = false; export const enableTrustedTypesIntegration = false; export const disableTextareaChildren = false; -export const disableModulePatternComponents = true; export const enableSuspenseAvoidThisFallback = true; export const enableSuspenseAvoidThisFallbackFizz = false; export const enableCPUSuspense = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 92c3eb0b38653..c309500c3f00b 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -82,8 +82,6 @@ export const enablePostpone = false; // Need to remove it. export const disableCommentsAsDOMContainers = false; -export const disableModulePatternComponents = true; - export const enableCreateEventHandleAPI = true; export const enableScopeAPI = true; diff --git a/yarn.lock b/yarn.lock index 71c9174c1c238..2c4c5af830120 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6997,6 +6997,7 @@ eslint-plugin-no-unsanitized@3.1.2: "eslint-plugin-react-internal@link:./scripts/eslint-rules": version "0.0.0" + uid "" eslint-plugin-react@^6.7.1: version "6.10.3" @@ -12991,7 +12992,7 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.8: object-assign "^4.1.1" scheduler "^0.20.2" -react-is@^16.8.1, react-is@^17.0.1, "react-is@npm:react-is": +react-is@^16.8.1, react-is@^17.0.1, react-is@^18.0.0, react-is@^18.2.0, "react-is@npm:react-is": version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==