mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Remove module pattern function component support (#27742)
The module pattern
```
function MyComponent() {
return {
render() {
return this.state.foo
}
}
}
```
has been deprecated for approximately 5 years now. This PR removes
support for this pattern. It also simplifies a number of code paths in
particular related to the concept of `IndeterminateComponent` types.
This commit is contained in:
+1
-1
@@ -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,
|
||||
|
||||
@@ -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 <Child />;
|
||||
},
|
||||
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 <div />;
|
||||
}
|
||||
Child.contextTypes = {
|
||||
x: PropTypes.number,
|
||||
};
|
||||
|
||||
const root = ReactDOMClient.createRoot(document.createElement('div'));
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Parent ref={c => c && log.push('ref')} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Warning: The <Parent /> 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(<Parent ref={c => 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 = {};
|
||||
|
||||
@@ -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 <div>{test}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
it('should not support module pattern components', async () => {
|
||||
function Child({test}) {
|
||||
return {
|
||||
render() {
|
||||
return <div>{test}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const el = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(el);
|
||||
await expect(async () => {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Child test="test" />);
|
||||
});
|
||||
}).rejects.toThrow(
|
||||
'Objects are not valid as a React child (found: object with keys {render}).',
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Warning: The <Child /> 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(<Child test="test" />);
|
||||
});
|
||||
}).rejects.toThrow(
|
||||
'Objects are not valid as a React child (found: object with keys {render}).',
|
||||
);
|
||||
|
||||
expect(el.textContent).toBe('');
|
||||
});
|
||||
} else {
|
||||
it('should support module pattern components', () => {
|
||||
function Child({test}) {
|
||||
return {
|
||||
render() {
|
||||
return <div>{test}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const el = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(el);
|
||||
expect(() => {
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(<Child test="test" />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Warning: The <Child /> 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('test');
|
||||
});
|
||||
}
|
||||
expect(el.textContent).toBe('');
|
||||
});
|
||||
|
||||
it('should use default values for undefined props', async () => {
|
||||
class Component extends React.Component {
|
||||
|
||||
@@ -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 <div>{`count:${this.state.count}`}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const el = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(el);
|
||||
expect(() => {
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(<Child />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Warning: The <Child /> 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 <div>{`count:${this.state.count}`}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
Child.getDerivedStateFromProps = (props, prevState) => {
|
||||
return {
|
||||
count: prevState.count + props.incrementBy,
|
||||
};
|
||||
};
|
||||
|
||||
const el = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(el);
|
||||
await act(() => {
|
||||
root.render(<Child incrementBy={0} />);
|
||||
});
|
||||
|
||||
expect(el.textContent).toBe('count:1');
|
||||
await act(() => {
|
||||
root.render(<Child incrementBy={2} />);
|
||||
});
|
||||
expect(el.textContent).toBe('count:3');
|
||||
|
||||
await act(() => {
|
||||
root.render(<Child incrementBy={1} />);
|
||||
});
|
||||
expect(el.textContent).toBe('count:4');
|
||||
});
|
||||
}
|
||||
|
||||
it('should not support setState in componentWillUnmount', async () => {
|
||||
let subscription;
|
||||
class A extends React.Component {
|
||||
|
||||
+7
-20
@@ -627,23 +627,9 @@ describe('ReactDOMServerIntegration', () => {
|
||||
checkFooDiv(await render(<ClassComponent />));
|
||||
});
|
||||
|
||||
if (require('shared/ReactFeatureFlags').disableModulePatternComponents) {
|
||||
itThrowsWhenRendering(
|
||||
'factory components',
|
||||
async render => {
|
||||
const FactoryComponent = () => {
|
||||
return {
|
||||
render: function () {
|
||||
return <div>foo</div>;
|
||||
},
|
||||
};
|
||||
};
|
||||
await render(<FactoryComponent />, 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(<FactoryComponent />, 1));
|
||||
});
|
||||
}
|
||||
await render(<FactoryComponent />, 1);
|
||||
},
|
||||
'Objects are not valid as a React child (found: object with keys {render})',
|
||||
);
|
||||
});
|
||||
|
||||
describe('component hierarchies', function () {
|
||||
|
||||
@@ -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 <div>{this.props.children}</div>;
|
||||
},
|
||||
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(
|
||||
<ErrorBoundary>
|
||||
<BrokenComponentWillMountWithContext />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'Warning: The <BrokenComponentWillMountWithContext /> 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 <ErrorMessage message={error.message} />;
|
||||
|
||||
@@ -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 <div>{this.props.children}</div>;
|
||||
},
|
||||
UNSAFE_componentWillMount() {
|
||||
throw new Error('Hello');
|
||||
},
|
||||
};
|
||||
}
|
||||
BrokenComponentWillMountWithContext.childContextTypes = {
|
||||
foo: PropTypes.number,
|
||||
};
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<BrokenComponentWillMountWithContext />
|
||||
</ErrorBoundary>,
|
||||
container,
|
||||
),
|
||||
).toErrorDev([
|
||||
'Warning: The <BrokenComponentWillMountWithContext /> 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) {
|
||||
|
||||
-35
@@ -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 <div ref={this.elemRef} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let inst;
|
||||
await expect(async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(<Comp ref={current => (inst = current)} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Warning: The <Comp /> 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 {
|
||||
|
||||
+5
-16
@@ -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<any>) => 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') {
|
||||
|
||||
+71
-230
@@ -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 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);
|
||||
@@ -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__) {
|
||||
workInProgress.type = Component =
|
||||
resolveClassForHotReloading(Component);
|
||||
}
|
||||
return updateClassComponent(
|
||||
null,
|
||||
workInProgress,
|
||||
Component,
|
||||
resolvedProps,
|
||||
renderLanes,
|
||||
);
|
||||
} else {
|
||||
workInProgress.tag = FunctionComponent;
|
||||
if (__DEV__) {
|
||||
validateFunctionComponentInDev(workInProgress, Component);
|
||||
workInProgress.type = Component =
|
||||
resolveFunctionForHotReloading(Component);
|
||||
}
|
||||
child = updateFunctionComponent(
|
||||
return updateFunctionComponent(
|
||||
null,
|
||||
workInProgress,
|
||||
Component,
|
||||
resolvedProps,
|
||||
renderLanes,
|
||||
);
|
||||
return child;
|
||||
}
|
||||
case ClassComponent: {
|
||||
if (__DEV__) {
|
||||
workInProgress.type = Component =
|
||||
resolveClassForHotReloading(Component);
|
||||
}
|
||||
child = updateClassComponent(
|
||||
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(
|
||||
|
||||
+7
-12
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1308,16 +1308,6 @@ describe('ReactHooks', () => {
|
||||
return <div />;
|
||||
});
|
||||
|
||||
function Factory() {
|
||||
return {
|
||||
state: {},
|
||||
render() {
|
||||
renderCount++;
|
||||
return <div />;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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(<Factory />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Warning: The <Factory /> 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(<Factory />);
|
||||
});
|
||||
expect(renderCount).toBe(1);
|
||||
|
||||
renderCount = 0;
|
||||
await act(() => {
|
||||
renderer.update(
|
||||
<StrictMode>
|
||||
<Factory />
|
||||
</StrictMode>,
|
||||
);
|
||||
});
|
||||
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
|
||||
renderCount = 0;
|
||||
await act(() => {
|
||||
renderer.update(
|
||||
<StrictMode>
|
||||
<Factory />
|
||||
</StrictMode>,
|
||||
);
|
||||
});
|
||||
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
|
||||
}
|
||||
|
||||
renderCount = 0;
|
||||
await act(() => {
|
||||
renderer.update(<HasHooks />);
|
||||
|
||||
@@ -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 <Text text={this.props.label + ': ' + count} />;
|
||||
},
|
||||
};
|
||||
}
|
||||
ReactNoop.render(<Counter />);
|
||||
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 <Counter /> 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 <Text text={count} />;
|
||||
}
|
||||
ReactNoop.render(<GoodCounter initialCount={10} />);
|
||||
await waitForAll([10]);
|
||||
});
|
||||
|
||||
it('throws when called outside the render phase', async () => {
|
||||
expect(() => {
|
||||
expect(() => useState(0)).toThrow(
|
||||
|
||||
@@ -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 />;
|
||||
},
|
||||
};
|
||||
}
|
||||
Recurse.contextTypes = {
|
||||
n: PropTypes.number,
|
||||
};
|
||||
Recurse.childContextTypes = {
|
||||
n: PropTypes.number,
|
||||
};
|
||||
|
||||
ReactNoop.render(<Recurse />);
|
||||
await expect(
|
||||
async () =>
|
||||
await waitForAll([
|
||||
'Recurse {}',
|
||||
'Recurse {"n":2}',
|
||||
'Recurse {"n":1}',
|
||||
'Recurse {"n":0}',
|
||||
]),
|
||||
).toErrorDev([
|
||||
'Warning: The <Recurse /> 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 () => {
|
||||
|
||||
-39
@@ -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(<Provider />);
|
||||
await expect(async () => {
|
||||
await waitForThrow('Oops!');
|
||||
}).toErrorDev([
|
||||
'Warning: The <Provider /> 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();
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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 <Child prop="A" />;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function Child({prop}) {
|
||||
return <h1>{prop}1</h1>;
|
||||
};
|
||||
|
||||
export default Parent;
|
||||
`);
|
||||
}).toErrorDev(
|
||||
'The <Parent /> 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 <Child prop="B" />;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function Child({prop}) {
|
||||
return <h1>{prop}2</h1>;
|
||||
};
|
||||
|
||||
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 = {};
|
||||
|
||||
+19
-80
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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__;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user