From e8d3c6b464221db79d8f08f78cd663400f1f5b04 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Fri, 29 Mar 2024 14:40:35 -0700 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. 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 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..6d6a94ecae19a 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) { @@ -3964,14 +3897,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( @@ -4094,6 +4019,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 ce18234fd37ca..e0873b7367553 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 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..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 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; } }