From 5998a775194f491afa5d3badd9afe9ceaf12845e Mon Sep 17 00:00:00 2001 From: Josh Story Date: Tue, 2 Apr 2024 17:42:43 -0700 Subject: [PATCH] Reland #28672: Remove IndeterminateComponent (#28681) This PR relands #28672 on top of the flag removal and the test demonstrating a breakage in Suspense for legacy mode. 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. While removing IndeterminateComponent type Seb and I discovered that we needed a concept of IncompleteFunctionComponent to support Suspense in legacy mode. This new work tag is only needed as long as legacy mode is around and ideally any code that considers this tag will be excludable from OSS builds once we land extra gates using `disableLegacyMode` flag. --- .../src/backend/renderer.js | 14 +- .../src/backend/types.js | 1 + .../__tests__/ReactCompositeComponent-test.js | 19 +- packages/react-reconciler/src/ReactFiber.js | 21 +- .../src/ReactFiberBeginWork.js | 260 +++++++----------- .../src/ReactFiberClassComponent.js | 18 +- .../src/ReactFiberCompleteWork.js | 4 +- .../src/ReactFiberComponentStack.js | 2 - .../src/ReactFiberHydrationDiffs.js | 2 - .../react-reconciler/src/ReactFiberThrow.js | 8 + .../src/ReactFiberWorkLoop.js | 8 - .../react-reconciler/src/ReactWorkTags.js | 5 +- .../ReactSubtreeFlagsWarning-test.js | 8 +- .../src/getComponentNameFromFiber.js | 2 - packages/react-server/src/ReactFizzServer.js | 34 +-- 15 files changed, 149 insertions(+), 257 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index b1e5dac527e61..803ef4b76b549 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -225,7 +225,8 @@ export function getInternalReactConstants(version: string): { HostSingleton: 27, // Same as above HostText: 6, IncompleteClassComponent: 17, - IndeterminateComponent: 2, + IncompleteFunctionComponent: 28, + IndeterminateComponent: 2, // removed in 19.0.0 LazyComponent: 16, LegacyHiddenComponent: 23, MemoComponent: 14, @@ -259,6 +260,7 @@ export function getInternalReactConstants(version: string): { HostSingleton: -1, // Doesn't exist yet HostText: 6, IncompleteClassComponent: 17, + IncompleteFunctionComponent: -1, // Doesn't exist yet IndeterminateComponent: 2, LazyComponent: 16, LegacyHiddenComponent: 24, @@ -292,6 +294,7 @@ export function getInternalReactConstants(version: string): { HostSingleton: -1, // Doesn't exist yet HostText: 6, IncompleteClassComponent: 17, + IncompleteFunctionComponent: -1, // Doesn't exist yet IndeterminateComponent: 2, LazyComponent: 16, LegacyHiddenComponent: -1, @@ -325,6 +328,7 @@ export function getInternalReactConstants(version: string): { HostSingleton: -1, // Doesn't exist yet HostText: 8, IncompleteClassComponent: -1, // Doesn't exist yet + IncompleteFunctionComponent: -1, // Doesn't exist yet IndeterminateComponent: 4, LazyComponent: -1, // Doesn't exist yet LegacyHiddenComponent: -1, @@ -358,6 +362,7 @@ export function getInternalReactConstants(version: string): { HostSingleton: -1, // Doesn't exist yet HostText: 6, IncompleteClassComponent: -1, // Doesn't exist yet + IncompleteFunctionComponent: -1, // Doesn't exist yet IndeterminateComponent: 0, LazyComponent: -1, // Doesn't exist yet LegacyHiddenComponent: -1, @@ -391,6 +396,7 @@ export function getInternalReactConstants(version: string): { CacheComponent, ClassComponent, IncompleteClassComponent, + IncompleteFunctionComponent, FunctionComponent, IndeterminateComponent, ForwardRef, @@ -459,6 +465,7 @@ export function getInternalReactConstants(version: string): { return 'Cache'; case ClassComponent: case IncompleteClassComponent: + case IncompleteFunctionComponent: case FunctionComponent: case IndeterminateComponent: return getDisplayName(resolvedType); @@ -624,6 +631,7 @@ export function attach( HostComponent, HostText, IncompleteClassComponent, + IncompleteFunctionComponent, IndeterminateComponent, LegacyHiddenComponent, MemoComponent, @@ -1061,6 +1069,7 @@ export function attach( case ClassComponent: case IncompleteClassComponent: return ElementTypeClass; + case IncompleteFunctionComponent: case FunctionComponent: case IndeterminateComponent: return ElementTypeFunction; @@ -3059,6 +3068,7 @@ export function attach( switch (tag) { case ClassComponent: case IncompleteClassComponent: + case IncompleteFunctionComponent: case IndeterminateComponent: case FunctionComponent: global.$type = type; @@ -3193,6 +3203,7 @@ export function attach( tag === ClassComponent || tag === FunctionComponent || tag === IncompleteClassComponent || + tag === IncompleteFunctionComponent || tag === IndeterminateComponent || tag === MemoComponent || tag === ForwardRef || @@ -3540,6 +3551,7 @@ export function attach( case IndeterminateComponent: global.$r = stateNode; break; + case IncompleteFunctionComponent: case FunctionComponent: global.$r = { hooks, diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index df45122f6314f..c006cf53e3d5b 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -58,6 +58,7 @@ export type WorkTagMap = { HostSingleton: WorkTag, HostText: WorkTag, IncompleteClassComponent: WorkTag, + IncompleteFunctionComponent: WorkTag, IndeterminateComponent: WorkTag, LazyComponent: WorkTag, LegacyHiddenComponent: WorkTag, 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 5b8dc06fb5f69..d676966907826 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -41,7 +41,6 @@ import { import {NoFlags, Placement, StaticMask} from './ReactFiberFlags'; import {ConcurrentRoot} from './ReactRootTags'; import { - IndeterminateComponent, ClassComponent, HostRoot, HostComponent, @@ -245,19 +244,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. @@ -348,7 +338,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); @@ -489,7 +478,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 9e745a69c5c83..fbf7988cdea78 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, @@ -67,6 +66,7 @@ import { SimpleMemoComponent, LazyComponent, IncompleteClassComponent, + IncompleteFunctionComponent, ScopeComponent, OffscreenComponent, LegacyHiddenComponent, @@ -114,7 +114,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 +247,12 @@ import { } from './ReactFiberClassComponent'; import {resolveDefaultProps} from './ReactFiberLazyComponent'; import { - resolveLazyComponentTag, createFiberFromTypeAndProps, createFiberFromFragment, createFiberFromOffscreen, createWorkInProgress, isSimpleFunctionComponent, + isFunctionClassComponent, } from './ReactFiber'; import { retryDehydratedSuspenseBoundary, @@ -303,7 +308,6 @@ export const SelectiveHydrationException: mixed = new Error( let didReceiveUpdate: boolean = false; let didWarnAboutBadClass; -let didWarnAboutModulePatternComponent; let didWarnAboutContextTypeOnFunctionComponent; let didWarnAboutGetDerivedStateOnFunctionComponent; let didWarnAboutFunctionRefs; @@ -314,7 +318,6 @@ let didWarnAboutDefaultPropsOnFunctionComponent; if (__DEV__) { didWarnAboutBadClass = ({}: {[string]: boolean}); - didWarnAboutModulePatternComponent = ({}: {[string]: boolean}); didWarnAboutContextTypeOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutGetDerivedStateOnFunctionComponent = ({}: {[string]: boolean}); didWarnAboutFunctionRefs = ({}: {[string]: boolean}); @@ -1044,6 +1047,26 @@ function markRef(current: Fiber | null, workInProgress: Fiber) { } } +function mountIncompleteFunctionComponent( + _current: null | Fiber, + workInProgress: Fiber, + Component: any, + nextProps: any, + renderLanes: Lanes, +) { + resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress); + + workInProgress.tag = FunctionComponent; + + return updateFunctionComponent( + null, + workInProgress, + Component, + nextProps, + renderLanes, + ); +} + function updateFunctionComponent( current: null | Fiber, workInProgress: Fiber, @@ -1051,6 +1074,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 +1757,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 +1874,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) { @@ -3972,14 +3905,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( @@ -4102,6 +4027,21 @@ function beginWork( renderLanes, ); } + case IncompleteFunctionComponent: { + const Component = workInProgress.type; + const unresolvedProps = workInProgress.pendingProps; + const resolvedProps = + workInProgress.elementType === Component + ? unresolvedProps + : resolveDefaultProps(Component, unresolvedProps); + return mountIncompleteFunctionComponent( + current, + workInProgress, + Component, + resolvedProps, + renderLanes, + ); + } case SuspenseListComponent: { return updateSuspenseListComponent(current, workInProgress, renderLanes); } 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..01087e3696279 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, @@ -66,6 +65,7 @@ import { SimpleMemoComponent, LazyComponent, IncompleteClassComponent, + IncompleteFunctionComponent, ScopeComponent, OffscreenComponent, LegacyHiddenComponent, @@ -949,10 +949,10 @@ function completeWork( // for hydration. popTreeContext(workInProgress); switch (workInProgress.tag) { - case IndeterminateComponent: case LazyComponent: case SimpleMemoComponent: case FunctionComponent: + case IncompleteFunctionComponent: case ForwardRef: case Fragment: case Mode: 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/ReactFiberThrow.js b/packages/react-reconciler/src/ReactFiberThrow.js index 3bb54f73e80d6..5b40a872b1f85 100644 --- a/packages/react-reconciler/src/ReactFiberThrow.js +++ b/packages/react-reconciler/src/ReactFiberThrow.js @@ -20,6 +20,7 @@ import { ClassComponent, HostRoot, IncompleteClassComponent, + IncompleteFunctionComponent, FunctionComponent, ForwardRef, SimpleMemoComponent, @@ -262,6 +263,13 @@ function markSuspenseBoundaryShouldCapture( update.tag = ForceUpdate; enqueueUpdate(sourceFiber, update, SyncLane); } + } else if (sourceFiber.tag === FunctionComponent) { + const currentSourceFiber = sourceFiber.alternate; + if (currentSourceFiber === null) { + // This is a new mount. Change the tag so it's not mistaken for a + // completed function component. + sourceFiber.tag = IncompleteFunctionComponent; + } } // The source fiber did not complete. Mark it with Sync priority to diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index ea8869a4a8c13..2570f857c7d2d 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`. @@ -3807,7 +3800,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..a4d4eb1751548 100644 --- a/packages/react-reconciler/src/ReactWorkTags.js +++ b/packages/react-reconciler/src/ReactWorkTags.js @@ -35,11 +35,11 @@ export type WorkTag = | 24 | 25 | 26 - | 27; + | 27 + | 28; 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; @@ -64,3 +64,4 @@ export const CacheComponent = 24; export const TracingMarkerComponent = 25; export const HostHoistable = 26; export const HostSingleton = 27; +export const IncompleteFunctionComponent = 28; 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 788544b731e12..a06ef06e8bb2b 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -1410,7 +1410,6 @@ function renderClassComponent( } const didWarnAboutBadClass: {[string]: boolean} = {}; -const didWarnAboutModulePatternComponent: {[string]: boolean} = {}; const didWarnAboutContextTypeOnFunctionComponent: {[string]: boolean} = {}; const didWarnAboutGetDerivedStateOnFunctionComponent: {[string]: boolean} = {}; let didWarnAboutReassigningProps = false; @@ -1418,9 +1417,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, @@ -1465,33 +1462,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( @@ -1817,7 +1787,7 @@ function renderElement( renderClassComponent(request, task, keyPath, type, props); return; } else { - renderIndeterminateComponent(request, task, keyPath, type, props); + renderFunctionComponent(request, task, keyPath, type, props); return; } }