Validate memo() inner propTypes and warn about shadowing

This commit is contained in:
Dan Abramov
2018-11-21 18:05:07 +00:00
parent 908526e428
commit 6dc78114ec
2 changed files with 74 additions and 0 deletions
@@ -12,6 +12,7 @@
'use strict';
let PropTypes;
let React;
let ReactFeatureFlags;
let ReactNoop;
@@ -22,6 +23,7 @@ describe('memo', () => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
PropTypes = require('prop-types');
React = require('react');
ReactNoop = require('react-noop-renderer');
({Suspense} = React);
@@ -321,6 +323,58 @@ describe('memo', () => {
{withoutStack: true},
);
});
it('validates propTypes declared on the inner component', () => {
function FnInner(props) {
return props.inner + props.outer;
}
FnInner.propTypes = {inner: PropTypes.number.isRequired};
const Fn = React.memo(FnInner);
expect(() => {
ReactNoop.render(<Fn inner="2" outer="3" />);
}).toWarnDev(
'Invalid prop `inner` of type `string` supplied to `FnInner`, expected `number`.\n' +
' in FnInner (at **)',
);
});
it('validates propTypes declared on the outer component', () => {
function FnInner(props) {
return props.inner + props.outer;
}
const Fn = React.memo(FnInner);
Fn.propTypes = {outer: PropTypes.number.isRequired};
expect(() => {
ReactNoop.render(<Fn inner="2" outer="3" />);
}).toWarnDev(
'Invalid prop `outer` of type `string` supplied to `FnInner`, expected `number`.\n' +
' in FnInner (at **)',
);
});
it('warns about propTypes declared on both the outer and the inner component', () => {
function FnInner(props) {
return props.inner + props.outer;
}
FnInner.propTypes = {inner: PropTypes.number.isRequired};
const Fn = React.memo(FnInner);
Fn.propTypes = {outer: PropTypes.number.isRequired};
expect(() => {
ReactNoop.render(<Fn inner="2" outer="3" />);
}).toWarnDev(
[
'React.memo(FnInner): `propTypes` are defined both on the `React.memo()` result ' +
'and on the inner `FnInner` component. Remove either one of these `propTypes` definitions.',
'Invalid prop `outer` of type `string` supplied to `FnInner`, expected `number`.\n' +
' in FnInner (at **)',
// Outer propTypes shadow the inner ones and aren't validated in this case.
],
{withoutStack: 1},
);
// Deduplication
ReactNoop.render(<Fn inner="2" outer="3" />);
});
});
}
});
@@ -22,6 +22,7 @@ import {
REACT_FRAGMENT_TYPE,
REACT_ELEMENT_TYPE,
REACT_LAZY_TYPE,
REACT_MEMO_TYPE,
} from 'shared/ReactSymbols';
import checkPropTypes from 'prop-types/checkPropTypes';
import warning from 'shared/warning';
@@ -34,9 +35,11 @@ import ReactDebugCurrentFrame, {
} from './ReactDebugCurrentFrame';
let propTypesMisspellWarningShown;
let didWarnAboutDuplicatePropTypes;
if (__DEV__) {
propTypesMisspellWarningShown = false;
didWarnAboutDuplicatePropTypes = {};
}
function getDeclarationErrorAddendum() {
@@ -188,6 +191,23 @@ function getPropTypes(type) {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE:
return type.propTypes;
case REACT_MEMO_TYPE:
const outerPropTypes = type.propTypes;
const innerPropTypes = type.type.propTypes;
if (outerPropTypes && innerPropTypes) {
const componentName = getComponentName(type);
if (!didWarnAboutDuplicatePropTypes[componentName]) {
didWarnAboutDuplicatePropTypes[componentName] = true;
warning(
false,
'React.memo(%s): `propTypes` are defined both on the `React.memo()` result and on the ' +
'inner `%s` component. Remove either one of these `propTypes` definitions.',
componentName,
componentName,
);
}
}
return outerPropTypes || innerPropTypes;
case REACT_LAZY_TYPE:
const resolvedType = refineResolvedLazyComponent(type);
if (resolvedType !== null) {