diff --git a/package.json b/package.json index 80c0f2a..885066f 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "homepage": "https://reactjs.org/", "dependencies": { "object-assign": "^4.1.1", - "prop-types": "^15.7.2", "react-is": "^16.12.0" }, "devDependencies": { diff --git a/src/ReactShallowRenderer.js b/src/ReactShallowRenderer.js index 3b8db26..f270a49 100644 --- a/src/ReactShallowRenderer.js +++ b/src/ReactShallowRenderer.js @@ -12,12 +12,12 @@ import {isForwardRef, isMemo, ForwardRef} from 'react-is'; import describeComponentFrame from './shared/describeComponentFrame'; import getComponentName from './shared/getComponentName'; import shallowEqual from './shared/shallowEqual'; -import checkPropTypes from 'prop-types/checkPropTypes'; +import checkPropTypes from './shared/checkPropTypes'; import ReactSharedInternals from './shared/ReactSharedInternals'; import {error} from './shared/consoleWithStackDev'; import is from './shared/objectIs'; -const {ReactCurrentDispatcher} = ReactSharedInternals; +const {ReactCurrentDispatcher, ReactDebugCurrentFrame} = ReactSharedInternals; const RE_RENDER_LIMIT = 25; @@ -503,91 +503,100 @@ See https://fb.me/react-invalid-hook-call for tips about how to debug and fix th this._context = getMaskedContext(elementType.contextTypes, context); // Inner memo component props aren't currently validated in createElement. - if (isMemo(element) && elementType.propTypes) { - currentlyValidatingElement = element; - checkPropTypes( - elementType.propTypes, - element.props, - 'prop', - getComponentName(elementType), - getStackAddendum, - ); + let prevGetStack; + if (process.env.NODE_ENV !== 'production') { + prevGetStack = ReactDebugCurrentFrame.getCurrentStack; + ReactDebugCurrentFrame.getCurrentStack = getStackAddendum; } - - if (this._instance) { - this._updateClassComponent(elementType, element, this._context); - } else { - if (shouldConstruct(elementType)) { - this._instance = new elementType( + try { + if (isMemo(element) && elementType.propTypes) { + currentlyValidatingElement = element; + checkPropTypes( + elementType.propTypes, element.props, - this._context, - this._updater, + 'prop', + getComponentName(elementType), ); - if (typeof elementType.getDerivedStateFromProps === 'function') { - const partialState = elementType.getDerivedStateFromProps.call( - null, + } + + if (this._instance) { + this._updateClassComponent(elementType, element, this._context); + } else { + if (shouldConstruct(elementType)) { + this._instance = new elementType( element.props, - this._instance.state, + this._context, + this._updater, ); - if (partialState != null) { - this._instance.state = Object.assign( - {}, + if (typeof elementType.getDerivedStateFromProps === 'function') { + const partialState = elementType.getDerivedStateFromProps.call( + null, + element.props, this._instance.state, - partialState, ); + if (partialState != null) { + this._instance.state = Object.assign( + {}, + this._instance.state, + partialState, + ); + } } - } - if (elementType.contextTypes) { - currentlyValidatingElement = element; - checkPropTypes( - elementType.contextTypes, - this._context, - 'context', - getName(elementType, this._instance), - getStackAddendum, - ); + if (elementType.contextTypes) { + currentlyValidatingElement = element; + checkPropTypes( + elementType.contextTypes, + this._context, + 'context', + getName(elementType, this._instance), + ); - currentlyValidatingElement = null; - } + currentlyValidatingElement = null; + } - this._mountClassComponent(elementType, element, this._context); - } else { - let shouldRender = true; - if (isMemo(element) && previousElement !== null) { - // This is a Memo component that is being re-rendered. - const compare = element.type.compare || shallowEqual; - if (compare(previousElement.props, element.props)) { - shouldRender = false; + this._mountClassComponent(elementType, element, this._context); + } else { + let shouldRender = true; + if (isMemo(element) && previousElement !== null) { + // This is a Memo component that is being re-rendered. + const compare = element.type.compare || shallowEqual; + if (compare(previousElement.props, element.props)) { + shouldRender = false; + } } - } - if (shouldRender) { - const prevDispatcher = ReactCurrentDispatcher.current; - ReactCurrentDispatcher.current = this._dispatcher; - try { - // elementType could still be a ForwardRef if it was - // nested inside Memo. - if (elementType.$$typeof === ForwardRef) { - if (!(typeof elementType.render === 'function')) { - throw Error( - `forwardRef requires a render function but was given ${typeof elementType.render}.`, + if (shouldRender) { + const prevDispatcher = ReactCurrentDispatcher.current; + ReactCurrentDispatcher.current = this._dispatcher; + try { + // elementType could still be a ForwardRef if it was + // nested inside Memo. + if (elementType.$$typeof === ForwardRef) { + if (!(typeof elementType.render === 'function')) { + throw Error( + `forwardRef requires a render function but was given ${typeof elementType.render}.`, + ); + } + this._rendered = elementType.render.call( + undefined, + element.props, + element.ref, ); + } else { + this._rendered = elementType(element.props, this._context); } - this._rendered = elementType.render.call( - undefined, - element.props, - element.ref, - ); - } else { - this._rendered = elementType(element.props, this._context); + } finally { + ReactCurrentDispatcher.current = prevDispatcher; } - } finally { - ReactCurrentDispatcher.current = prevDispatcher; + this._finishHooks(element, context); } - this._finishHooks(element, context); } } - } + } finally { + if (process.env.NODE_ENV !== 'production') { + ReactDebugCurrentFrame.getCurrentStack = prevGetStack; + } + } this._rendering = false; this._updater._invokeCallbacks(); diff --git a/src/shared/checkPropTypes.js b/src/shared/checkPropTypes.js new file mode 100644 index 0000000..bee8117 --- /dev/null +++ b/src/shared/checkPropTypes.js @@ -0,0 +1,80 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import {error as consoleError} from './consoleWithStackDev'; + +let loggedTypeFailures = {}; + +export default function checkPropTypes( + typeSpecs, + values, + location, + componentName, +) { + if (process.env.NODE_ENV !== 'production') { + let has = Function.call.bind(Object.prototype.hasOwnProperty); + for (let typeSpecName in typeSpecs) { + if (has(typeSpecs, typeSpecName)) { + let error; + // Prop type validation may throw. In case they do, we don't want to + // fail the render phase where it didn't fail before. So we log it. + // After these have been cleaned up, we'll let them throw. + try { + // This is intentionally an invariant that gets caught. It's the same + // behavior as without this statement except with a better message. + if (typeof typeSpecs[typeSpecName] !== 'function') { + let err = Error( + (componentName || 'React class') + + ': ' + + location + + ' type `' + + typeSpecName + + '` is invalid; ' + + 'it must be a function, usually from the `prop-types` package, but received `' + + typeof typeSpecs[typeSpecName] + + '`.' + + 'This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.', + ); + err.name = 'Invariant Violation'; + throw err; + } + error = typeSpecs[typeSpecName]( + values, + typeSpecName, + componentName, + location, + null, + 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED', + ); + } catch (ex) { + error = ex; + } + if (error && !(error instanceof Error)) { + consoleError( + '%s: type specification of %s' + + ' `%s` is invalid; the type checker ' + + 'function must return `null` or an `Error` but returned a %s. ' + + 'You may have forgotten to pass an argument to the type checker ' + + 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + + 'shape all require an argument).', + componentName || 'React class', + location, + typeSpecName, + typeof error, + ); + } + if (error instanceof Error && !(error.message in loggedTypeFailures)) { + // Only monitor this failure once because there tends to be a lot of the + // same error. + loggedTypeFailures[error.message] = true; + consoleError('Failed %s type: %s', location, error.message); + } + } + } + } +} diff --git a/yarn.lock b/yarn.lock index afad75e..410d16f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3553,7 +3553,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.3" -prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.6.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==