Enable warning for defaultProps on function components for everyone (#25699)

This also fixes a gap where were weren't warning on memo components.
This commit is contained in:
Sebastian Markbåge
2022-11-17 12:22:23 -05:00
committed by GitHub
parent 6fb8133ed3
commit 7b17f7bbf3
17 changed files with 209 additions and 79 deletions
@@ -898,7 +898,11 @@ describe('ReactHooksInspectionIntegration', () => {
await LazyFoo;
Scheduler.unstable_flushAll();
expect(() => {
Scheduler.unstable_flushAll();
}).toErrorDev([
'Foo: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
]);
const childFiber = renderer.root._currentFiber();
const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
+68 -38
View File
@@ -291,6 +291,18 @@ describe('ReactDOMFizzServer', () => {
}
it('should asynchronously load a lazy component', async () => {
const originalConsoleError = console.error;
const mockError = jest.fn();
console.error = (...args) => {
if (args.length > 1) {
if (typeof args[1] === 'object') {
mockError(args[0].split('\n')[0]);
return;
}
}
mockError(...args.map(normalizeCodeLocInfo));
};
let resolveA;
const LazyA = React.lazy(() => {
return new Promise(r => {
@@ -313,48 +325,66 @@ describe('ReactDOMFizzServer', () => {
punctuation: '!',
};
await act(async () => {
const {pipe} = renderToPipeableStream(
try {
await act(async () => {
const {pipe} = renderToPipeableStream(
<div>
<div>
<Suspense fallback={<Text text="Loading..." />}>
<LazyA text="Hello" />
</Suspense>
</div>
<div>
<Suspense fallback={<Text text="Loading..." />}>
<LazyB text="world" />
</Suspense>
</div>
</div>,
);
pipe(writable);
});
expect(getVisibleChildren(container)).toEqual(
<div>
<div>
<Suspense fallback={<Text text="Loading..." />}>
<LazyA text="Hello" />
</Suspense>
</div>
<div>
<Suspense fallback={<Text text="Loading..." />}>
<LazyB text="world" />
</Suspense>
</div>
<div>Loading...</div>
<div>Loading...</div>
</div>,
);
await act(async () => {
resolveA({default: Text});
});
expect(getVisibleChildren(container)).toEqual(
<div>
<div>Hello</div>
<div>Loading...</div>
</div>,
);
await act(async () => {
resolveB({default: TextWithPunctuation});
});
expect(getVisibleChildren(container)).toEqual(
<div>
<div>Hello</div>
<div>world!</div>
</div>,
);
pipe(writable);
});
expect(getVisibleChildren(container)).toEqual(
<div>
<div>Loading...</div>
<div>Loading...</div>
</div>,
);
await act(async () => {
resolveA({default: Text});
});
expect(getVisibleChildren(container)).toEqual(
<div>
<div>Hello</div>
<div>Loading...</div>
</div>,
);
await act(async () => {
resolveB({default: TextWithPunctuation});
});
expect(getVisibleChildren(container)).toEqual(
<div>
<div>Hello</div>
<div>world!</div>
</div>,
);
if (__DEV__) {
expect(mockError).toHaveBeenCalledWith(
'Warning: %s: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.%s',
'TextWithPunctuation',
'\n in TextWithPunctuation (at **)\n' +
' in Lazy (at **)\n' +
' in Suspense (at **)\n' +
' in div (at **)\n' +
' in div (at **)',
);
} else {
expect(mockError).not.toHaveBeenCalled();
}
} finally {
console.error = originalConsoleError;
}
});
it('#23331: does not warn about hydration mismatches if something suspended in an earlier sibling', async () => {
@@ -10,7 +10,6 @@
'use strict';
let React;
let ReactFeatureFlags;
let ReactNoop;
let Scheduler;
let JSXDEVRuntime;
@@ -19,19 +18,11 @@ describe('ReactDeprecationWarnings', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
if (__DEV__) {
JSXDEVRuntime = require('react/jsx-dev-runtime');
}
ReactFeatureFlags.warnAboutDefaultPropsOnFunctionComponents = true;
ReactFeatureFlags.warnAboutStringRefs = true;
});
afterEach(() => {
ReactFeatureFlags.warnAboutDefaultPropsOnFunctionComponents = false;
ReactFeatureFlags.warnAboutStringRefs = false;
});
it('should warn when given defaultProps', () => {
@@ -51,6 +42,27 @@ describe('ReactDeprecationWarnings', () => {
);
});
it('should warn when given defaultProps on a memoized function', () => {
const MemoComponent = React.memo(function FunctionalComponent(props) {
return null;
});
MemoComponent.defaultProps = {
testProp: true,
};
ReactNoop.render(
<div>
<MemoComponent />
</div>,
);
expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
'Warning: FunctionalComponent: Support for defaultProps ' +
'will be removed from memo components in a future major ' +
'release. Use JavaScript default parameters instead.',
);
});
it('should warn when given string refs', () => {
class RefComponent extends React.Component {
render() {
@@ -74,9 +86,7 @@ describe('ReactDeprecationWarnings', () => {
);
});
it('should not warn when owner and self are the same for string refs', () => {
ReactFeatureFlags.warnAboutStringRefs = false;
it('should warn when owner and self are the same for string refs', () => {
class RefComponent extends React.Component {
render() {
return null;
@@ -87,7 +97,11 @@ describe('ReactDeprecationWarnings', () => {
return <RefComponent ref="refComponent" __self={this} />;
}
}
ReactNoop.renderLegacySyncRoot(<Component />);
expect(() => {
ReactNoop.renderLegacySyncRoot(<Component />);
}).toErrorDev([
'Component "Component" contains the string ref "refComponent". Support for string refs will be removed in a future major release.',
]);
expect(Scheduler).toFlushWithoutYielding();
});
@@ -367,11 +367,12 @@ describe('ReactFunctionComponent', () => {
Child.defaultProps = {test: 2};
Child.propTypes = {test: PropTypes.string};
expect(() => ReactTestUtils.renderIntoDocument(<Child />)).toErrorDev(
expect(() => ReactTestUtils.renderIntoDocument(<Child />)).toErrorDev([
'Warning: Child: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
'Warning: Failed prop type: Invalid prop `test` of type `number` ' +
'supplied to `Child`, expected `string`.\n' +
' in Child (at **)',
);
]);
});
it('should receive context', () => {
@@ -496,6 +496,20 @@ function updateMemoComponent(
getComponentNameFromType(type),
);
}
if (
warnAboutDefaultPropsOnFunctionComponents &&
Component.defaultProps !== undefined
) {
const componentName = getComponentNameFromType(type) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
console.error(
'%s: Support for defaultProps will be removed from memo components ' +
'in a future major release. Use JavaScript default parameters instead.',
componentName,
);
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
}
}
}
const child = createFiberFromTypeAndProps(
Component.type,
@@ -496,6 +496,20 @@ function updateMemoComponent(
getComponentNameFromType(type),
);
}
if (
warnAboutDefaultPropsOnFunctionComponents &&
Component.defaultProps !== undefined
) {
const componentName = getComponentNameFromType(type) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
console.error(
'%s: Support for defaultProps will be removed from memo components ' +
'in a future major release. Use JavaScript default parameters instead.',
componentName,
);
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
}
}
}
const child = createFiberFromTypeAndProps(
Component.type,
@@ -293,7 +293,12 @@ describe('ReactLazy', () => {
await Promise.resolve();
expect(Scheduler).toFlushAndYield(['Hi']);
expect(() => expect(Scheduler).toFlushAndYield(['Hi'])).toErrorDev(
'Warning: T: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.',
);
expect(root).toMatchRenderedOutput('Hi');
T.defaultProps = {text: 'Hi again'};
@@ -343,7 +348,14 @@ describe('ReactLazy', () => {
await Promise.resolve();
expect(Scheduler).toFlushAndYield(['Lazy', 'Sibling', 'A']);
expect(() =>
expect(Scheduler).toFlushAndYield(['Lazy', 'Sibling', 'A']),
).toErrorDev(
'Warning: LazyImpl: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.',
);
expect(root).toMatchRenderedOutput('SiblingA');
// Lazy should not re-render
@@ -643,7 +655,12 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('Hi Bye');
await Promise.resolve();
expect(Scheduler).toFlushAndYield(['Hi Bye']);
expect(() => expect(Scheduler).toFlushAndYield(['Hi Bye'])).toErrorDev(
'Warning: T: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.',
);
expect(root).toMatchRenderedOutput('Hi Bye');
root.update(
@@ -732,7 +749,11 @@ describe('ReactLazy', () => {
);
});
async function verifyInnerPropTypesAreChecked(Add) {
async function verifyInnerPropTypesAreChecked(
Add,
shouldWarnAboutFunctionDefaultProps,
shouldWarnAboutMemoDefaultProps,
) {
const LazyAdd = lazy(() => fakeImport(Add));
expect(() => {
LazyAdd.propTypes = {};
@@ -753,15 +774,28 @@ describe('ReactLazy', () => {
);
expect(Scheduler).toFlushAndYield(['Loading...']);
expect(root).not.toMatchRenderedOutput('22');
// Mount
await Promise.resolve();
expect(() => {
Scheduler.unstable_flushAll();
}).toErrorDev([
'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
]);
}).toErrorDev(
shouldWarnAboutFunctionDefaultProps
? [
'Add: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
]
: shouldWarnAboutMemoDefaultProps
? [
'Add: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
]
: [
'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
],
);
expect(root).toMatchRenderedOutput('22');
// Update
@@ -792,7 +826,7 @@ describe('ReactLazy', () => {
Add.defaultProps = {
innerWithDefault: 42,
};
await verifyInnerPropTypesAreChecked(Add);
await verifyInnerPropTypesAreChecked(Add, true);
});
it('respects propTypes on function component without defaultProps', async () => {
@@ -874,7 +908,7 @@ describe('ReactLazy', () => {
Add.defaultProps = {
innerWithDefault: 42,
};
await verifyInnerPropTypesAreChecked(Add);
await verifyInnerPropTypesAreChecked(Add, false, true);
});
it('respects propTypes on outer memo component without defaultProps', async () => {
@@ -901,7 +935,7 @@ describe('ReactLazy', () => {
Add.defaultProps = {
innerWithDefault: 42,
};
await verifyInnerPropTypesAreChecked(React.memo(Add));
await verifyInnerPropTypesAreChecked(React.memo(Add), true);
});
it('respects propTypes on inner memo component without defaultProps', async () => {
@@ -944,9 +978,10 @@ describe('ReactLazy', () => {
await Promise.resolve();
expect(() => {
expect(Scheduler).toFlushAndYield(['Inner default text']);
}).toErrorDev(
}).toErrorDev([
'T: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
'The prop `text` is marked as required in `T`, but its value is `undefined`',
);
]);
expect(root).toMatchRenderedOutput('Inner default text');
// Update
@@ -1058,7 +1093,11 @@ describe('ReactLazy', () => {
// Mount
await Promise.resolve();
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev(
'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
);
expect(root).toMatchRenderedOutput('4');
// Update (shallowly equal)
@@ -1142,7 +1181,12 @@ describe('ReactLazy', () => {
// Mount
await Promise.resolve();
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev([
'Memo: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
]);
expect(root).toMatchRenderedOutput('4');
// Update
+11 -2
View File
@@ -75,6 +75,7 @@ describe('memo', () => {
}
ReactNoop.render(<Outer />);
expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev([
'App: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
'Warning: Function components cannot be given refs. Attempts to access ' +
'this ref will fail.',
]);
@@ -441,7 +442,11 @@ describe('memo', () => {
);
expect(Scheduler).toFlushAndYield(['Loading...']);
await Promise.resolve();
expect(Scheduler).toFlushAndYield([15]);
expect(() => {
expect(Scheduler).toFlushAndYield([15]);
}).toErrorDev([
'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
]);
expect(ReactNoop.getChildren()).toEqual([span(15)]);
// Should bail out because props have not changed
@@ -552,7 +557,11 @@ describe('memo', () => {
<Outer />
</div>,
);
expect(Scheduler).toFlushWithoutYielding();
expect(() => {
expect(Scheduler).toFlushWithoutYielding();
}).toErrorDev([
'Inner: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
]);
// Mount
expect(() => {
+1 -1
View File
@@ -213,7 +213,7 @@ export const disableTextareaChildren = false;
// Part of the simplification of React.createElement so we can eventually move
// from React.createElement to React.jsx
// https://github.com/reactjs/rfcs/blob/createlement-rfc/text/0000-create-element-changes.md
export const warnAboutDefaultPropsOnFunctionComponents = false; // deprecate later, not 18.0
export const warnAboutDefaultPropsOnFunctionComponents = true; // deprecate later, not 18.0
// Enables a warning when trying to spread a 'key' to an element;
// a deprecated pattern we want to get rid of in the future
@@ -41,7 +41,7 @@ export const warnAboutDeprecatedLifecycles = true;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
@@ -31,7 +31,7 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
@@ -31,7 +31,7 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
@@ -31,7 +31,7 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
@@ -31,7 +31,7 @@ export const disableInputAttributeSyncing = false;
export const enableScopeAPI = true;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
@@ -31,7 +31,7 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = false;
export const enableCreateEventHandleAPI = false;
export const enableSuspenseCallback = false;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = false;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
@@ -31,7 +31,7 @@ export const enableSchedulerDebugging = false;
export const enableScopeAPI = true;
export const enableCreateEventHandleAPI = true;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const warnAboutStringRefs = true;
export const disableLegacyContext = __EXPERIMENTAL__;
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
@@ -67,7 +67,7 @@ export const enableSchedulerDebugging = true;
export const warnAboutDeprecatedLifecycles = true;
export const disableLegacyContext = __EXPERIMENTAL__;
export const warnAboutStringRefs = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
export const warnAboutDefaultPropsOnFunctionComponents = true;
export const enableGetInspectorDataForInstanceInProduction = false;
export const enableCache = true;