diff --git a/package.json b/package.json index 7a1f67b..8092d7b 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "immutability-helper": "^2.1.2", "preact-render-to-string": "^3.6.0", "preact-transition-group": "^1.1.0", - "proptypes": "^0.14.3", + "prop-types": "^15.5.8", "standalone-react-addons-pure-render-mixin": "^0.1.1" } } diff --git a/rollup.config.js b/rollup.config.js index 9221e3d..d14decd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -21,7 +21,7 @@ export default { useStrict: false, globals: { 'preact': 'preact', - 'proptypes': 'PropTypes' + 'prop-types': 'PropTypes' }, plugins: [ format==='umd' && memory({ diff --git a/src/index.js b/src/index.js index aa9c2da..ef298f9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import PropTypes from 'proptypes'; +import PropTypes from 'prop-types'; import { render as preactRender, cloneElement as preactCloneElement, h, Component as PreactComponent, options } from 'preact'; const version = '15.1.0'; // trick libraries to think we are react @@ -270,10 +270,35 @@ function statelessComponentHook(Ctor) { return Wrapped; } +function validatePropTypes (vnode) { + if (DEV) { + let componentClass = typeof vnode.nodeName === "function" + ? vnode.nodeName + : vnode.type; + + if (typeof componentClass !== "function") return; + let name = componentClass.displayName || componentClass.name; + let propTypes = componentClass.propTypes; + if (propTypes) { + let props = vnode.props; + if ( + !(vnode.props && vnode.props.children) && + vnode.children && + vnode.children.length + ) { + props.children = vnode.children; + } + propsHook(props); + PropTypes.checkPropTypes(propTypes, props, 'prop', name); + } + } +} function createElement(...args) { upgradeToVNodes(args, 2); - return normalizeVNode(h(...args)); + let node = h(...args); + validatePropTypes(node); + return normalizeVNode(node); } @@ -293,7 +318,6 @@ function normalizeVNode(vnode) { } applyEventNormalization(vnode); - return vnode; } @@ -315,10 +339,11 @@ function cloneElement(element, props, ...children) { else if (props && props.children) { cloneArgs.push(props.children); } - return normalizeVNode(preactCloneElement(...cloneArgs)); + let newNode = preactCloneElement(...cloneArgs); + validatePropTypes(newNode); + return normalizeVNode(newNode); } - function isValidElement(element) { return element && ((element instanceof VNode) || element.$$typeof===REACT_ELEMENT_TYPE); } @@ -489,7 +514,7 @@ function multihook(hooks, skipDuplicates) { function newComponentHook(props, context) { - propsHook.call(this, props, context); + propsHook(props, context); this.componentWillReceiveProps = multihook([propsHook, this.componentWillReceiveProps || 'componentWillReceiveProps']); this.render = multihook([propsHook, beforeRender, this.render || 'render', afterRender]); } @@ -509,24 +534,8 @@ function propsHook(props, context) { props.children[0] = props.children; } } - - // add proptype checking - if (DEV) { - let ctor = typeof this==='function' ? this : this.constructor, - propTypes = this.propTypes || ctor.propTypes; - if (propTypes) { - for (let prop in propTypes) { - if (propTypes.hasOwnProperty(prop) && typeof propTypes[prop]==='function') { - const displayName = this.displayName || ctor.name; - let err = propTypes[prop](props, prop, displayName, 'prop'); - if (err) console.error(new Error(err.message || err)); - } - } - } - } } - function beforeRender(props) { currentComponent = this; } diff --git a/test/component.js b/test/component.js index cb2a877..d290d68 100644 --- a/test/component.js +++ b/test/component.js @@ -173,13 +173,13 @@ describe('components', () => { }); describe('propTypes', () => { - function checkPropTypes(Foo) { + function checkPropTypes(Foo, name = 'Foo') { sinon.stub(console, 'error'); - React.render(, scratch); - expect(console.error).to.have.been.calledWithMatch({ - message: 'Required prop `func` was not specified in `Foo`.' - }); + expect(console.error).to.have.been.calledWithMatch( + 'Warning: Failed prop type: The prop `func` is marked as required in `' + name + '`, but its value is `undefined`.' + ); + expect(console.error).to.have.been.called; console.error.reset(); @@ -187,9 +187,17 @@ describe('components', () => { expect(console.error).not.to.have.been.called; React.render({}} bool="one" />, scratch); - expect(console.error).to.have.been.calledWithMatch({ - message: 'Invalid prop `bool` of type `string` supplied to `Foo`, expected `boolean`.' - }); + expect(console.error).to.have.been.calledWithMatch( + 'Warning: Failed prop type: Invalid prop `bool` of type `string` supplied to `' + name + '`, expected `boolean`.' + ); + + console.error.reset(); + + const clone = React.cloneElement(); + React.render(clone, scratch); + expect(console.error).to.have.been.calledWithMatch( + 'Warning: Failed prop type: Invalid prop `func` of type `string` supplied to `' + name + '`, expected `function`.' + ); console.error.restore(); } @@ -209,7 +217,7 @@ describe('components', () => { }); it('should support propTypes for createClass components', () => { - const Foo = React.createClass({ + const Bar = React.createClass({ propTypes: { func: React.PropTypes.func.isRequired, bool: React.PropTypes.bool @@ -217,24 +225,24 @@ describe('components', () => { render: () =>
}); - checkPropTypes(Foo); + checkPropTypes(Bar, 'Bar'); }); it('should support propTypes for pure components', () => { - function Foo() { return
; } - Foo.propTypes = { + function Baz() { return
; } + Baz.propTypes = { func: React.PropTypes.func.isRequired, bool: React.PropTypes.bool }; - checkPropTypes(Foo); + checkPropTypes(Baz, 'Baz'); - const Foo2 = () =>
; - Foo2.displayName = 'Foo'; - Foo2.propTypes = { + const Bip = () =>
; + Bip.displayName = 'Bip'; + Bip.propTypes = { func: React.PropTypes.func.isRequired, bool: React.PropTypes.bool }; - checkPropTypes(Foo2); + checkPropTypes(Bip, 'Bip'); }); });