From c3980a6d08257d39a08bed8b116d53a56344166b Mon Sep 17 00:00:00 2001 From: Manas Date: Tue, 29 Dec 2015 15:40:20 +0530 Subject: [PATCH] Warns on access of `props.key` and `props.ref` --- .../classic/element/ReactElement.js | 60 ++++++++++++++++++- .../element/__tests__/ReactElement-test.js | 58 ++++++++++++++++++ 2 files changed, 115 insertions(+), 3 deletions(-) diff --git a/src/isomorphic/classic/element/ReactElement.js b/src/isomorphic/classic/element/ReactElement.js index 44aa50f7d2a15..65e95683f746d 100644 --- a/src/isomorphic/classic/element/ReactElement.js +++ b/src/isomorphic/classic/element/ReactElement.js @@ -14,6 +14,7 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var assign = require('Object.assign'); +var warning = require('warning'); var canDefineProperty = require('canDefineProperty'); // The Symbol used to tag the ReactElement type. If there is no native Symbol @@ -29,6 +30,8 @@ var RESERVED_PROPS = { __source: true, }; +var specialPropKeyWarningShown, specialPropRefWarningShown; + /** * Factory method to create a new React element. This no longer adheres to * the class pattern, so do not use new to call it. Also, no instanceof check @@ -123,8 +126,15 @@ ReactElement.createElement = function(type, config, children) { var source = null; if (config != null) { - ref = config.ref === undefined ? null : config.ref; - key = config.key === undefined ? null : '' + config.key; + if (__DEV__) { + ref = !config.hasOwnProperty('ref') || + Object.getOwnPropertyDescriptor(config, 'ref').get ? null : config.ref; + key = !config.hasOwnProperty('key') || + Object.getOwnPropertyDescriptor(config, 'key').get ? null : '' + config.key; + } else { + ref = config.ref === undefined ? null : config.ref; + key = config.key === undefined ? null : '' + config.key; + } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object @@ -158,7 +168,51 @@ ReactElement.createElement = function(type, config, children) { } } } - + if (__DEV__) { + // Create dummy `key` and `ref` property to `props` to warn users + // against its use + if (typeof props.$$typeof === 'undefined' || + props.$$typeof !== REACT_ELEMENT_TYPE) { + if (!props.hasOwnProperty('key')) { + Object.defineProperty(props, 'key', { + get: function() { + if (!specialPropKeyWarningShown) { + specialPropKeyWarningShown = true; + warning( + false, + '%s: `key` is not a prop. Trying to access it will result ' + + 'in `undefined` being returned. If you need to access the same ' + + 'value within the child component, you should pass it as a different ' + + 'prop. (https://fb.me/react-special-props)', + 'displayName' in type ? type.displayName: 'Element' + ); + } + return undefined; + }, + configurable: true, + }); + } + if (!props.hasOwnProperty('ref')) { + Object.defineProperty(props, 'ref', { + get: function() { + if (!specialPropRefWarningShown) { + specialPropRefWarningShown = true; + warning( + false, + '%s: `ref` is not a prop. Trying to access it will result ' + + 'in `undefined` being returned. If you need to access the same ' + + 'value within the child component, you should pass it as a different ' + + 'prop. (https://fb.me/react-special-props)', + 'displayName' in type ? type.displayName: 'Element' + ); + } + return undefined; + }, + configurable: true, + }); + } + } + } return ReactElement( type, key, diff --git a/src/isomorphic/classic/element/__tests__/ReactElement-test.js b/src/isomorphic/classic/element/__tests__/ReactElement-test.js index 1166ab28eaa75..124e80e0c22d2 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElement-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElement-test.js @@ -58,6 +58,64 @@ describe('ReactElement', function() { expect(element.props).toEqual(expectation); }); + it('should warn when `key` is being accessed', function() { + spyOn(console, 'error'); + var container = document.createElement('div'); + var Child = React.createClass({ + render: function() { + return
{this.props.key}
; + }, + }); + var Parent = React.createClass({ + render: function() { + return ( +
+ + + +
+ ); + }, + }); + expect(console.error.calls.length).toBe(0); + ReactDOM.render(, container); + expect(console.error.calls.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'Child: `key` is not a prop. Trying to access it will result ' + + 'in `undefined` being returned. If you need to access the same ' + + 'value within the child component, you should pass it as a different ' + + 'prop. (https://fb.me/react-special-props)' + ); + }); + + it('should warn when `ref` is being accessed', function() { + spyOn(console, 'error'); + var container = document.createElement('div'); + var Child = React.createClass({ + render: function() { + return
{this.props.ref}
; + }, + }); + var Parent = React.createClass({ + render: function() { + return ( +
+ +
+ ); + }, + }); + expect(console.error.calls.length).toBe(0); + ReactDOM.render(, container); + expect(console.error.calls.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'Child: `ref` is not a prop. Trying to access it will result ' + + 'in `undefined` being returned. If you need to access the same ' + + 'value within the child component, you should pass it as a different ' + + 'prop. (https://fb.me/react-special-props)' + ); + }); + it('allows a string to be passed as the type', function() { var element = React.createFactory('div')(); expect(element.type).toBe('div');