diff --git a/README.md b/README.md index 5927cc2..70766f9 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,10 @@ MyComponent.propTypes = { // A value of any data type requiredAny: PropTypes.any.isRequired, + // You can use `notNull` like `isRequired` + // to allow undefined but not null + optionalObjectNotNull: PropTypes.object.notNull, + // You can also specify a custom validator. It should return an Error // object if the validation fails. Don't `console.warn` or throw, as this // won't work inside `oneOfType`. @@ -142,7 +146,7 @@ For example: ```js "dependencies": { - "prop-types": "^15.5.7" + "prop-types": "^15.5.7" } ``` @@ -150,10 +154,10 @@ For libraries, we *also* recommend leaving it in `dependencies`: ```js "dependencies": { - "prop-types": "^15.5.7" + "prop-types": "^15.5.7" }, "peerDependencies": { - "react": "^15.5.0" + "react": "^15.5.0" } ``` diff --git a/__tests__/PropTypesDevelopmentStandalone-test.js b/__tests__/PropTypesDevelopmentStandalone-test.js index 9684d56..08d662f 100644 --- a/__tests__/PropTypesDevelopmentStandalone-test.js +++ b/__tests__/PropTypesDevelopmentStandalone-test.js @@ -91,6 +91,27 @@ function typeCheckPass(declaration, value) { expect(message).toBe(null); } +function typeCheckNullFail(declaration, value) { + const propTypes = { + testProp: declaration, + }; + var mustNotBeNullMsg = 'The prop `testProp` in `testComponent`' + + ' must not be `null`.'; + const message1 = getPropTypeWarningMessage( + propTypes, + {testProp: null}, + 'testComponent' + ); + expect(message1).toContain(mustNotBeNullMsg); + + const message2 = getPropTypeWarningMessage( + propTypes, + {testProp: undefined}, + 'testComponent' + ); + expect(message2).toBe(null); +} + function expectThrowsInDevelopment(declaration, value) { resetWarningCache(); var props = {testProp: value}; @@ -214,6 +235,10 @@ describe('PropTypesDevelopmentStandalone', () => { typeCheckPass(PropTypes.string, undefined); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail(PropTypes.string.notNull); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.string.isRequired); }); @@ -278,6 +303,10 @@ describe('PropTypesDevelopmentStandalone', () => { typeCheckPass(PropTypes.any, undefined); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail(PropTypes.any.notNull); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.any.isRequired); }); @@ -372,6 +401,12 @@ describe('PropTypesDevelopmentStandalone', () => { typeCheckPass(PropTypes.arrayOf(PropTypes.number), undefined); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail( + PropTypes.arrayOf(PropTypes.number).notNull + ); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues( PropTypes.arrayOf(PropTypes.number).isRequired, @@ -441,6 +476,10 @@ describe('PropTypesDevelopmentStandalone', () => { typeCheckPass(PropTypes.element, undefined); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail(PropTypes.element.notNull); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.element.isRequired); }); @@ -540,6 +579,10 @@ describe('PropTypesDevelopmentStandalone', () => { typeCheckPass(PropTypes.instanceOf(String), undefined); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail(PropTypes.instanceOf(String).notNull); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.instanceOf(String).isRequired); }); @@ -636,6 +679,10 @@ describe('PropTypesDevelopmentStandalone', () => { typeCheckPass(PropTypes.node, undefined); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail(PropTypes.node.notNull); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.node.isRequired); }); @@ -753,6 +800,12 @@ describe('PropTypesDevelopmentStandalone', () => { typeCheckPass(PropTypes.objectOf(PropTypes.number), undefined); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail( + PropTypes.objectOf(PropTypes.number).notNull + ); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues( PropTypes.objectOf(PropTypes.number).isRequired, @@ -830,6 +883,10 @@ describe('PropTypesDevelopmentStandalone', () => { typeCheckPass(PropTypes.oneOf(['red', 'blue']), undefined); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail(PropTypes.oneOf(['red', 'blue']).notNull); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.oneOf(['red', 'blue']).isRequired); }); @@ -919,6 +976,12 @@ describe('PropTypesDevelopmentStandalone', () => { ); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]).notNull + ); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues( PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, @@ -1017,6 +1080,12 @@ describe('PropTypesDevelopmentStandalone', () => { ); }); + it('should warn when null is not allowed', () => { + typeCheckNullFail( + PropTypes.shape({key: PropTypes.number}).notNull + ); + }); + it('should warn for missing required values', () => { typeCheckFailRequiredValues( PropTypes.shape({key: PropTypes.number}).isRequired, diff --git a/factoryWithTypeCheckers.js b/factoryWithTypeCheckers.js index 4da647e..c38e3ac 100644 --- a/factoryWithTypeCheckers.js +++ b/factoryWithTypeCheckers.js @@ -150,7 +150,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) { var manualPropTypeCallCache = {}; var manualPropTypeWarningCount = 0; } - function checkType(isRequired, props, propName, componentName, location, propFullName, secret) { + function checkType(isRequired, notNull, props, propName, componentName, location, propFullName, secret) { componentName = componentName || ANONYMOUS; propFullName = propFullName || propName; @@ -192,6 +192,8 @@ module.exports = function(isValidElement, throwOnDirectAccess) { return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.')); } return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.')); + } else if (notNull && props[propName] === null) { + return new PropTypeError('The ' + location + ' `' + propFullName + '` ' + ('in `' + componentName + '` must not be `null`.')); } return null; } else { @@ -199,8 +201,9 @@ module.exports = function(isValidElement, throwOnDirectAccess) { } } - var chainedCheckType = checkType.bind(null, false); - chainedCheckType.isRequired = checkType.bind(null, true); + var chainedCheckType = checkType.bind(null, false, false); + chainedCheckType.isRequired = checkType.bind(null, true, true); + chainedCheckType.notNull = checkType.bind(null, false, true); return chainedCheckType; }