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__/ReactCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js index 5959cdefccfe3..2e56a911a0c38 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js @@ -223,23 +223,16 @@ describe('ReactCompositeComponent', () => { 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.", + await act(() => { + root.render(); + }); + }).rejects.toThrow( + 'Objects are not valid as a React child (found: object with keys {render}).', ); expect(el.textContent).toBe(''); }); + it('should use default values for undefined props', async () => { class Component extends React.Component { static defaultProps = {prop: 'testKey'}; 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 f6dce003e842f..6e5f53edb796b 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, @@ -114,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, @@ -242,12 +246,12 @@ import { } from './ReactFiberClassComponent'; import {resolveDefaultProps} from './ReactFiberLazyComponent'; import { - resolveLazyComponentTag, createFiberFromTypeAndProps, createFiberFromFragment, createFiberFromOffscreen, createWorkInProgress, isSimpleFunctionComponent, + isFunctionClassComponent, } from './ReactFiber'; import { retryDehydratedSuspenseBoundary, @@ -303,7 +307,6 @@ export const SelectiveHydrationException: mixed = new Error( let didReceiveUpdate: boolean = false; let didWarnAboutBadClass; -let didWarnAboutModulePatternComponent; let didWarnAboutContextTypeOnFunctionComponent; let didWarnAboutGetDerivedStateOnFunctionComponent; let didWarnAboutFunctionRefs; @@ -314,7 +317,6 @@ let didWarnAboutDefaultPropsOnFunctionComponent; if (__DEV__) { didWarnAboutBadClass = ({}: {[string]: boolean}); - didWarnAboutModulePatternComponent = ({}: {[string]: boolean}); didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutFunctionRefs = ({}: {[string]: boolean}); @@ -1051,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 was removed in React 19. ' + + 'Use React.createContext() with React.useContext() instead.', + getComponentNameFromType(Component) || 'Unknown', + ); + } + } + } + let context; if (!disableLegacyContext) { const unmaskedContext = getUnmaskedContext(workInProgress, Component, true); @@ -1697,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 ( @@ -1814,133 +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; - } - } - } - - // 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) { @@ -3964,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 b64f9f9e626c8..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) { 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 f9b18aff86485..41a7c8d7efa4d 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -90,7 +90,6 @@ import { } from './ReactTypeOfMode'; import { HostRoot, - IndeterminateComponent, ClassComponent, SuspenseComponent, SuspenseListComponent, @@ -2395,12 +2394,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`. @@ -3823,7 +3816,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__/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-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index f49fca69b6485..4567a1e80d13b 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -1387,7 +1387,6 @@ function renderClassComponent( } const didWarnAboutBadClass: {[string]: boolean} = {}; -const didWarnAboutModulePatternComponent: {[string]: boolean} = {}; const didWarnAboutContextTypeOnFunctionComponent: {[string]: boolean} = {}; const didWarnAboutGetDerivedStateOnFunctionComponent: {[string]: boolean} = {}; let didWarnAboutReassigningProps = false; @@ -1395,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, @@ -1442,33 +1439,6 @@ function renderIndeterminateComponent( const actionStateCount = getActionStateCount(); 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; - } - } - } - - // Proceed under the assumption that this is a function component if (__DEV__) { if (disableLegacyContext && Component.contextTypes) { console.error( @@ -1794,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; } }