diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 23df1d25bd..33e7137f9b 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -13,6 +13,7 @@ import type {FiberRoot} from './ReactFiberRoot'; import type {ExpirationTime} from './ReactFiberExpirationTime'; import type {SuspenseState} from './ReactFiberSuspenseComponent'; +import React from 'react'; import checkPropTypes from 'prop-types/checkPropTypes'; import { @@ -50,11 +51,11 @@ import { debugRenderPhaseSideEffectsForStrictMode, enableProfilerTimer, } from 'shared/ReactFeatureFlags'; +import isValidElementType from 'shared/isValidElementType'; import invariant from 'shared/invariant'; import shallowEqual from 'shared/shallowEqual'; import getComponentName from 'shared/getComponentName'; import ReactStrictModeWarnings from './ReactStrictModeWarnings'; -import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent'; import {REACT_LAZY_TYPE} from 'shared/ReactSymbols'; import warning from 'shared/warning'; import warningWithoutStack from 'shared/warningWithoutStack'; @@ -214,23 +215,6 @@ function updateForwardRef( nextProps: any, renderExpirationTime: ExpirationTime, ) { - if (__DEV__) { - if (workInProgress.type !== workInProgress.elementType) { - // Lazy component props can't be validated in createElement - // because they're only guaranteed to be resolved here. - const innerPropTypes = Component.propTypes; - if (innerPropTypes) { - checkPropTypes( - innerPropTypes, - nextProps, // Resolved props - 'prop', - getComponentName(Component), - getCurrentFiberStackInDev, - ); - } - } - } - const render = Component.render; const ref = workInProgress.ref; @@ -292,20 +276,6 @@ function updateMemoComponent( renderExpirationTime, ); } - if (__DEV__) { - const innerPropTypes = type.propTypes; - if (innerPropTypes) { - // Inner memo component props aren't currently validated in createElement. - // We could move it there, but we'd still need this for lazy code path. - checkPropTypes( - innerPropTypes, - nextProps, // Resolved props - 'prop', - getComponentName(type), - getCurrentFiberStackInDev, - ); - } - } let child = createFiberFromTypeAndProps( Component.type, null, @@ -319,21 +289,6 @@ function updateMemoComponent( workInProgress.child = child; return child; } - if (__DEV__) { - const type = Component.type; - const innerPropTypes = type.propTypes; - if (innerPropTypes) { - // Inner memo component props aren't currently validated in createElement. - // We could move it there, but we'd still need this for lazy code path. - checkPropTypes( - innerPropTypes, - nextProps, // Resolved props - 'prop', - getComponentName(type), - getCurrentFiberStackInDev, - ); - } - } let currentChild = ((current.child: any): Fiber); // This is always exactly one child if (updateExpirationTime < renderExpirationTime) { // This will be the props with resolved defaultProps, @@ -371,30 +326,6 @@ function updateSimpleMemoComponent( updateExpirationTime, renderExpirationTime: ExpirationTime, ): null | Fiber { - if (__DEV__) { - if (workInProgress.type !== workInProgress.elementType) { - // Lazy component props can't be validated in createElement - // because they're only guaranteed to be resolved here. - let outerMemoType = workInProgress.elementType; - if (outerMemoType.$$typeof === REACT_LAZY_TYPE) { - // We warn when you define propTypes on lazy() - // so let's just skip over it to find memo() outer wrapper. - // Inner props for memo are validated later. - outerMemoType = refineResolvedLazyComponent(outerMemoType); - } - const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes; - if (outerPropTypes) { - checkPropTypes( - outerPropTypes, - nextProps, // Resolved (SimpleMemoComponent has no defaultProps) - 'prop', - getComponentName(outerMemoType), - getCurrentFiberStackInDev, - ); - } - // Inner propTypes will be validated in the function component path. - } - } if (current !== null && updateExpirationTime < renderExpirationTime) { const prevProps = current.memoizedProps; if ( @@ -484,23 +415,6 @@ function updateFunctionComponent( nextProps: any, renderExpirationTime, ) { - if (__DEV__) { - if (workInProgress.type !== workInProgress.elementType) { - // Lazy component props can't be validated in createElement - // because they're only guaranteed to be resolved here. - const innerPropTypes = Component.propTypes; - if (innerPropTypes) { - checkPropTypes( - innerPropTypes, - nextProps, // Resolved props - 'prop', - getComponentName(Component), - getCurrentFiberStackInDev, - ); - } - } - } - const unmaskedContext = getUnmaskedContext(workInProgress, Component, true); const context = getMaskedContext(workInProgress, unmaskedContext); @@ -535,23 +449,6 @@ function updateClassComponent( nextProps, renderExpirationTime: ExpirationTime, ) { - if (__DEV__) { - if (workInProgress.type !== workInProgress.elementType) { - // Lazy component props can't be validated in createElement - // because they're only guaranteed to be resolved here. - const innerPropTypes = Component.propTypes; - if (innerPropTypes) { - checkPropTypes( - innerPropTypes, - nextProps, // Resolved props - 'prop', - getComponentName(Component), - getCurrentFiberStackInDev, - ); - } - } - } - // 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. @@ -901,6 +798,15 @@ function mountLazyComponent( startWorkTimer(workInProgress); const resolvedProps = resolveDefaultProps(Component, props); let child; + + if (__DEV__) { + // Trigger propTypes validation for the resolved type now that + // createElement() can look deeper and find propTypes and defaultProps. + if (isValidElementType(Component)) { + React.createElement(Component, resolvedProps); + } + } + switch (resolvedTag) { case FunctionComponent: { child = updateFunctionComponent( @@ -933,20 +839,6 @@ function mountLazyComponent( break; } case MemoComponent: { - if (__DEV__) { - if (workInProgress.type !== workInProgress.elementType) { - const outerPropTypes = Component.propTypes; - if (outerPropTypes) { - checkPropTypes( - outerPropTypes, - resolvedProps, // Resolved for outer only - 'prop', - getComponentName(Component), - getCurrentFiberStackInDev, - ); - } - } - } child = updateMemoComponent( null, workInProgress, @@ -1877,20 +1769,6 @@ function beginWork( const unresolvedProps = workInProgress.pendingProps; // Resolve outer props first, then resolve inner props. let resolvedProps = resolveDefaultProps(type, unresolvedProps); - if (__DEV__) { - if (workInProgress.type !== workInProgress.elementType) { - const outerPropTypes = type.propTypes; - if (outerPropTypes) { - checkPropTypes( - outerPropTypes, - resolvedProps, // Resolved for outer only - 'prop', - getComponentName(type), - getCurrentFiberStackInDev, - ); - } - } - } resolvedProps = resolveDefaultProps(type.type, resolvedProps); return updateMemoComponent( current, diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index f25677580e..2fbdae21e6 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -15,10 +15,12 @@ import lowPriorityWarning from 'shared/lowPriorityWarning'; import isValidElementType from 'shared/isValidElementType'; import getComponentName from 'shared/getComponentName'; +import {refineResolvedLazyComponent} from 'shared/ReactLazyComponent'; import { getIteratorFn, REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE, + REACT_LAZY_TYPE, REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE, } from 'shared/ReactSymbols'; @@ -178,6 +180,61 @@ function validateChildKeys(node, parentType) { } } +function validatePropTypesRecursively(type, props) { + if (type === null || type === undefined || typeof type === 'string') { + return; + } + let propTypes; + let innerType; + if (typeof type === 'function') { + propTypes = type.propTypes; + } else if (typeof type === 'object') { + switch (type.$$typeof) { + case REACT_FORWARD_REF_TYPE: { + propTypes = type.propTypes; + break; + } + case REACT_LAZY_TYPE: { + propTypes = type.propTypes; + innerType = refineResolvedLazyComponent(type); + break; + } + case REACT_MEMO_TYPE: { + propTypes = type.propTypes; + innerType = type.type; + break; + } + } + } + // Common case. + if (propTypes) { + const name = getComponentName(type); + checkPropTypes( + propTypes, + props, + 'prop', + name, + ReactDebugCurrentFrame.getStackAddendum, + ); + } + // A type may have an inner type (e.g. React.memo or React.lazy). + // That's what the recursive case is for. + if (innerType) { + // Inner type may have its own defaultProps + let innerProps = props; + if (innerType && innerType.defaultProps) { + innerProps = {...props}; + const innerDefaultProps = innerType.defaultProps; + for (const propName in innerDefaultProps) { + if (innerProps[propName] === undefined) { + innerProps[propName] = innerDefaultProps[propName]; + } + } + } + validatePropTypesRecursively(innerType, innerProps); + } +} + /** * Given an element, validate that its props follow the propTypes definition, * provided by the type. @@ -189,45 +246,27 @@ function validatePropTypes(element) { if (type === null || type === undefined || typeof type === 'string') { return; } - const name = getComponentName(type); - let propTypes; + + setCurrentlyValidatingElement(element); + validatePropTypesRecursively(type, element.props); + setCurrentlyValidatingElement(null); + if (typeof type === 'function') { - propTypes = type.propTypes; - } else if ( - typeof type === 'object' && - (type.$$typeof === REACT_FORWARD_REF_TYPE || - // Note: Memo only checks outer props here. - // Inner props are checked in the reconciler. - type.$$typeof === REACT_MEMO_TYPE) - ) { - propTypes = type.propTypes; - } else { - return; - } - if (propTypes) { - setCurrentlyValidatingElement(element); - checkPropTypes( - propTypes, - element.props, - 'prop', - name, - ReactDebugCurrentFrame.getStackAddendum, - ); - setCurrentlyValidatingElement(null); - } else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) { - propTypesMisspellWarningShown = true; - warningWithoutStack( - false, - 'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?', - name || 'Unknown', - ); - } - if (typeof type.getDefaultProps === 'function') { - warningWithoutStack( - type.getDefaultProps.isReactClassApproved, - 'getDefaultProps is only used on classic React.createClass ' + - 'definitions. Use a static property named `defaultProps` instead.', - ); + if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) { + propTypesMisspellWarningShown = true; + warningWithoutStack( + false, + 'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?', + getComponentName(type) || 'Unknown', + ); + } + if (typeof type.getDefaultProps === 'function') { + warningWithoutStack( + type.getDefaultProps.isReactClassApproved, + 'getDefaultProps is only used on classic React.createClass ' + + 'definitions. Use a static property named `defaultProps` instead.', + ); + } } } diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index e3dd2bbf20..d83436c944 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -13,6 +13,7 @@ const UMD_PROFILING = bundleTypes.UMD_PROFILING; const HAS_NO_SIDE_EFFECTS_ON_IMPORT = false; // const HAS_SIDE_EFFECTS_ON_IMPORT = true; const importSideEffects = Object.freeze({ + react: HAS_NO_SIDE_EFFECTS_ON_IMPORT, 'prop-types/checkPropTypes': HAS_NO_SIDE_EFFECTS_ON_IMPORT, deepFreezeAndThrowOnMutationInDev: HAS_NO_SIDE_EFFECTS_ON_IMPORT, scheduler: HAS_NO_SIDE_EFFECTS_ON_IMPORT,