From ed760d15676edac4a2f565fc02460b022001ffa4 Mon Sep 17 00:00:00 2001 From: Nathan Hunzaker Date: Thu, 13 Oct 2016 09:20:14 -0400 Subject: [PATCH 01/10] Fix uncontrolled input decimal point "chopping" on number inputs, and validation warnings on email inputs (#7750) * Only assign defaultValue if it has changed. * Improve comment about reason for defaultValue conditional assignment (cherry picked from commit 0d20dcf9108811f632bfeb76a6bd3bf05d11865b) --- .../dom/client/wrappers/ReactDOMInput.js | 12 +++++++++- .../wrappers/__tests__/ReactDOMInput-test.js | 22 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/renderers/dom/client/wrappers/ReactDOMInput.js b/src/renderers/dom/client/wrappers/ReactDOMInput.js index 04814bd869..8d1ad6ec55 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMInput.js +++ b/src/renderers/dom/client/wrappers/ReactDOMInput.js @@ -213,7 +213,17 @@ var ReactDOMInput = { } } else { if (props.value == null && props.defaultValue != null) { - node.defaultValue = '' + props.defaultValue; + // In Chrome, assigning defaultValue to certain input types triggers input validation. + // For number inputs, the display value loses trailing decimal points. For email inputs, + // Chrome raises "The specified value is not a valid email address". + // + // Here we check to see if the defaultValue has actually changed, avoiding these problems + // when the user is inputting text + // + // https://github.com/facebook/react/issues/7253 + if (node.defaultValue !== '' + props.defaultValue) { + node.defaultValue = '' + props.defaultValue; + } } if (props.checked == null && props.defaultChecked != null) { node.defaultChecked = !!props.defaultChecked; diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js index 541f0671af..6e387fe3c8 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js @@ -42,6 +42,28 @@ describe('ReactDOMInput', () => { expect(node.value).toBe('0'); }); + it('only assigns defaultValue if it changes', () => { + class Test extends React.Component { + render() { + return (); + } + } + + var component = ReactTestUtils.renderIntoDocument(); + var node = ReactDOM.findDOMNode(component); + + Object.defineProperty(node, 'defaultValue', { + get() { + return '0'; + }, + set(value) { + throw new Error(`defaultValue was assigned ${value}, but it did not change!`); + }, + }); + + component.forceUpdate(); + }); + it('should display "true" for `defaultValue` of `true`', () => { var stub = ; stub = ReactTestUtils.renderIntoDocument(stub); From 278409db051ff040031462110880abf8d08dea36 Mon Sep 17 00:00:00 2001 From: Diego Muracciole Date: Sat, 22 Oct 2016 19:28:37 -0300 Subject: [PATCH 02/10] Injected Host Component classes are not being considered by the reconciler (#8050) * Consider Host Component classes when creating a new internal instance * Remove unused tagToComponentClass & injectComponentClasses from ReactHostComponent (cherry picked from commit 461a74115caf103b46d1ba93d3cba6fa7780d009) --- .../shared/stack/reconciler/ReactHostComponent.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/renderers/shared/stack/reconciler/ReactHostComponent.js b/src/renderers/shared/stack/reconciler/ReactHostComponent.js index 7a08f562d7..eebe5cba46 100644 --- a/src/renderers/shared/stack/reconciler/ReactHostComponent.js +++ b/src/renderers/shared/stack/reconciler/ReactHostComponent.js @@ -14,8 +14,6 @@ var invariant = require('invariant'); var genericComponentClass = null; -// This registry keeps track of wrapper classes around host tags. -var tagToComponentClass = {}; var textComponentClass = null; var ReactHostComponentInjection = { @@ -29,11 +27,6 @@ var ReactHostComponentInjection = { injectTextComponentClass: function(componentClass) { textComponentClass = componentClass; }, - // This accepts a keyed object with classes as values. Each key represents a - // tag. That particular tag will use this class instead of the generic one. - injectComponentClasses: function(componentClasses) { - Object.assign(tagToComponentClass, componentClasses); - }, }; /** From 4dd625a93f2d06f801a896d364da15719498cb95 Mon Sep 17 00:00:00 2001 From: Brandon Dail Date: Sat, 5 Nov 2016 11:47:54 -0500 Subject: [PATCH 03/10] Correctly render placeholder for textarea in IE11 (#8020) * Check if textContent should be set for textarea shouldSetNodeTextContent returns whether a node.textContent should be updated. Currently it only covers one case, which is to avoid setting the textContent if the text is empty and a placeholder exists. * Only set node.value if it's equal to initialValue In IE11 textContent is populated when the placeholder attribute is set. Without this check, we end up setting node.value equal to the placeholder text, causing the textarea to actually render with the text inside. This check makes sure that textContent is equal to our expected initialValue, which should be the case when using defaultValue. * Remove placeholder/textarea check, use contentToUse instead (cherry picked from commit e644faa6104ca6301c88ce4a535237b76450422b) --- .../dom/client/wrappers/ReactDOMTextarea.js | 10 ++++++++-- src/renderers/dom/shared/ReactDOMComponent.js | 14 ++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/renderers/dom/client/wrappers/ReactDOMTextarea.js b/src/renderers/dom/client/wrappers/ReactDOMTextarea.js index faee1b3985..3dbff23a60 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMTextarea.js +++ b/src/renderers/dom/client/wrappers/ReactDOMTextarea.js @@ -167,9 +167,15 @@ var ReactDOMTextarea = { // This is in postMount because we need access to the DOM node, which is not // available until after the component has mounted. var node = ReactDOMComponentTree.getNodeFromInstance(inst); + var textContent = node.textContent; - // Warning: node.value may be the empty string at this point (IE11) if placeholder is set. - node.value = node.textContent; // Detach value from defaultValue + // Only set node.value if textContent is equal to the expected + // initial value. In IE10/IE11 there is a bug where the placeholder attribute + // will populate textContent as well. + // https://developer.microsoft.com/microsoft-edge/platform/issues/101525/ + if (textContent === inst._wrapperState.initialValue) { + node.value = textContent; + } }, }; diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index eb56168f25..02759e1e2b 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -828,12 +828,18 @@ ReactDOMComponent.Mixin = { var contentToUse = CONTENT_TYPES[typeof props.children] ? props.children : null; var childrenToUse = contentToUse != null ? null : props.children; + // TODO: Validate that text is allowed as a child of this node if (contentToUse != null) { - // TODO: Validate that text is allowed as a child of this node - if (__DEV__) { - setAndValidateContentChildDev.call(this, contentToUse); + // Avoid setting textContent when the text is empty. In IE11 setting + // textContent on a text area will cause the placeholder to not + // show within the textarea until it has been focused and blurred again. + // https://github.com/facebook/react/issues/6731#issuecomment-254874553 + if (contentToUse !== '') { + if (__DEV__) { + setAndValidateContentChildDev.call(this, contentToUse); + } + DOMLazyTree.queueText(lazyTree, contentToUse); } - DOMLazyTree.queueText(lazyTree, contentToUse); } else if (childrenToUse != null) { var mountImages = this.mountChildren( childrenToUse, From c9a8c128bb6015cb998e2bc461466592961f507f Mon Sep 17 00:00:00 2001 From: Eoin Hennessy Date: Thu, 10 Nov 2016 16:02:48 +0000 Subject: [PATCH 04/10] Refactor `precacheChildNodes` slightly (#8018) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ‘fixes’ a bizarre IE9 script engine issue. #7803 (cherry picked from commit 6ce8f1f93c1d8758fcaa819ab18c70a7dd65b6aa) --- .../dom/client/ReactDOMComponentTree.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/renderers/dom/client/ReactDOMComponentTree.js b/src/renderers/dom/client/ReactDOMComponentTree.js index 8ff3260e85..d440446ace 100644 --- a/src/renderers/dom/client/ReactDOMComponentTree.js +++ b/src/renderers/dom/client/ReactDOMComponentTree.js @@ -22,6 +22,18 @@ var Flags = ReactDOMComponentFlags; var internalInstanceKey = '__reactInternalInstance$' + Math.random().toString(36).slice(2); +/** + * Check if a given node should be cached. + */ +function shouldPrecacheNode(node, nodeID) { + return (node.nodeType === 1 && + node.getAttribute(ATTR_NAME) === String(nodeID)) || + (node.nodeType === 8 && + node.nodeValue === ' react-text: ' + nodeID + ' ') || + (node.nodeType === 8 && + node.nodeValue === ' react-empty: ' + nodeID + ' '); +} + /** * Drill down (through composites and empty components) until we get a host or * host text component. @@ -87,12 +99,7 @@ function precacheChildNodes(inst, node) { } // We assume the child nodes are in the same order as the child instances. for (; childNode !== null; childNode = childNode.nextSibling) { - if ((childNode.nodeType === 1 && - childNode.getAttribute(ATTR_NAME) === String(childID)) || - (childNode.nodeType === 8 && - childNode.nodeValue === ' react-text: ' + childID + ' ') || - (childNode.nodeType === 8 && - childNode.nodeValue === ' react-empty: ' + childID + ' ')) { + if (shouldPrecacheNode(childNode, childID)) { precacheNode(childInst, childNode); continue outer; } From 5216190247929b28a851f9cac0346ed7d875a627 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Mon, 28 Nov 2016 17:57:37 -0800 Subject: [PATCH 05/10] Update release checklist (#8389) (cherry picked from commit a98e8227b88741fec0f0b9c7541beacbe624fcf9) --- grunt/tasks/release.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/grunt/tasks/release.js b/grunt/tasks/release.js index 6181d79202..8d8132bc04 100644 --- a/grunt/tasks/release.js +++ b/grunt/tasks/release.js @@ -108,11 +108,12 @@ function msg() { grunt.log.subhead('Release *almost* complete...'); var steps = [ 'Still todo:', - '* put files on CDN', - '* add starter pack (git add -f docs/downloads/react-version.zip)', - '* push changes to git repositories', - '* update docs branch variable in Travis CI', - '* publish npm modules', + '* add starter pack (`git add -f docs/downloads/react-version.zip`) and commit', + '* push this repo with tags', + '* push bower repo with tags', + '* run `npm-publish` in rrm', + '* create release on github', + '* for a major release, update docs branch variable in Travis CI', '* announce it on FB/Twitter/mailing list', ]; steps.forEach(function(ln) { From 3ec576ec1ddf51d4a61815cb5122221cb57e3cd3 Mon Sep 17 00:00:00 2001 From: Kurt Weiberth Date: Thu, 1 Dec 2016 08:37:14 -0800 Subject: [PATCH 06/10] add dependencies to react-test-renderer and react-addons (#8467) **What** and **Why**: * When using npm version 2, `object-assign` and `fbjs` were not getting properly installed * This PR adds `object-assign` and `fbjs` as explicit dependencies to both `react-test-renderer` and `react-addons` (cherry picked from commit 7cd26024ceffafc61a744325ef71b583db2ac1cd) --- packages/react-addons/package.json | 5 ++++- packages/react-test-renderer/package.json | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/react-addons/package.json b/packages/react-addons/package.json index b5744d100d..b82668cc28 100644 --- a/packages/react-addons/package.json +++ b/packages/react-addons/package.json @@ -8,7 +8,10 @@ "react-addon" ], "license": "BSD-3-Clause", - "dependencies": {}, + "dependencies": { + "fbjs": "^0.8.4", + "object-assign": "^4.1.0" + }, "peerDependencies": { "react": "^15.4.1" }, diff --git a/packages/react-test-renderer/package.json b/packages/react-test-renderer/package.json index 218dc8df85..de4ae74d07 100644 --- a/packages/react-test-renderer/package.json +++ b/packages/react-test-renderer/package.json @@ -14,6 +14,10 @@ "url": "https://github.com/facebook/react/issues" }, "homepage": "https://facebook.github.io/react/", + "dependencies": { + "fbjs": "^0.8.4", + "object-assign": "^4.1.0" + }, "peerDependencies": { "react": "^15.4.1" }, From b2ce4125f69791bbd45cdfd450c5f60a9b1a7419 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 17 Dec 2016 14:28:20 -0800 Subject: [PATCH 07/10] Merge pull request #8594 from bvaughn/dont-warn-about-getInitialState-on-class-if-state-set Don't warn about class components using getInitialState if state is set (cherry picked from commit 3c6d4bacddf9f37e0edeadc7116053e84290f934) --- .../ReactCoffeeScriptClass-test.coffee | 19 +++++++++++++++++++ .../class/__tests__/ReactES6Class-test.js | 15 +++++++++++++++ .../__tests__/ReactTypeScriptClass-test.ts | 18 ++++++++++++++++++ .../reconciler/ReactCompositeComponent.js | 3 ++- 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee b/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee index cf71005350..de03d875b0 100644 --- a/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee +++ b/src/isomorphic/modern/class/__tests__/ReactCoffeeScriptClass-test.coffee @@ -319,6 +319,25 @@ describe 'ReactCoffeeScriptClass', -> ) undefined + it 'does not warn about getInitialState() on class components + if state is also defined.', -> + spyOn console, 'error' + class Foo extends React.Component + constructor: (props) -> + super props + @state = bar: @props.initialValue + + getInitialState: -> + {} + + render: -> + span + className: 'foo' + + test React.createElement(Foo), 'SPAN', 'foo' + expect(console.error.calls.count()).toBe 0 + undefined + it 'should warn when misspelling shouldComponentUpdate', -> spyOn console, 'error' class NamedComponent extends React.Component diff --git a/src/isomorphic/modern/class/__tests__/ReactES6Class-test.js b/src/isomorphic/modern/class/__tests__/ReactES6Class-test.js index 2ff9091c1a..ca34089157 100644 --- a/src/isomorphic/modern/class/__tests__/ReactES6Class-test.js +++ b/src/isomorphic/modern/class/__tests__/ReactES6Class-test.js @@ -354,6 +354,21 @@ describe('ReactES6Class', () => { ); }); + it('does not warn about getInitialState() on class components if state is also defined.', () => { + spyOn(console, 'error'); + class Foo extends React.Component { + state = this.getInitialState(); + getInitialState() { + return {}; + } + render() { + return ; + } + } + test(, 'SPAN', 'foo'); + expect(console.error.calls.count()).toBe(0); + }); + it('should warn when misspelling shouldComponentUpdate', () => { spyOn(console, 'error'); diff --git a/src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts b/src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts index 0e90be788a..6ef95ccaa3 100644 --- a/src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts +++ b/src/isomorphic/modern/class/__tests__/ReactTypeScriptClass-test.ts @@ -454,6 +454,24 @@ describe('ReactTypeScriptClass', function() { ); }); + it('does not warn about getInitialState() on class components ' + + 'if state is also defined.', () => { + spyOn(console, 'error'); + + class Example extends React.Component { + state = {}; + getInitialState() { + return {}; + } + render() { + return React.createElement('span', {className: 'foo'}); + } + } + + test(React.createElement(Example), 'SPAN', 'foo'); + expect((console.error).calls.count()).toBe(0); + }); + it('should warn when misspelling shouldComponentUpdate', function() { spyOn(console, 'error'); diff --git a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js index 388111d90e..c0bd59c5f6 100644 --- a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js @@ -270,7 +270,8 @@ var ReactCompositeComponent = { // catch them here, at initialization time, instead. warning( !inst.getInitialState || - inst.getInitialState.isReactClassApproved, + inst.getInitialState.isReactClassApproved || + inst.state, 'getInitialState was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Did you mean to define a state property instead?', From d93cedad7fd824773ec1b88a1c61be7981501295 Mon Sep 17 00:00:00 2001 From: dfrownfelter Date: Mon, 19 Dec 2016 20:53:04 -0800 Subject: [PATCH 08/10] Delete fiveArgumentPooler (#8597) (cherry picked from commit b106ca0c8e5c1514b9fbfc69bd60c0c18ad47214) --- src/shared/utils/PooledClass.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/shared/utils/PooledClass.js b/src/shared/utils/PooledClass.js index 45040901ff..f2d0fc38d5 100644 --- a/src/shared/utils/PooledClass.js +++ b/src/shared/utils/PooledClass.js @@ -65,17 +65,6 @@ var fourArgumentPooler = function(a1, a2, a3, a4) { } }; -var fiveArgumentPooler = function(a1, a2, a3, a4, a5) { - var Klass = this; - if (Klass.instancePool.length) { - var instance = Klass.instancePool.pop(); - Klass.call(instance, a1, a2, a3, a4, a5); - return instance; - } else { - return new Klass(a1, a2, a3, a4, a5); - } -}; - var standardReleaser = function(instance) { var Klass = this; invariant( @@ -127,7 +116,6 @@ var PooledClass = { twoArgumentPooler: (twoArgumentPooler: Pooler), threeArgumentPooler: (threeArgumentPooler: Pooler), fourArgumentPooler: (fourArgumentPooler: Pooler), - fiveArgumentPooler: (fiveArgumentPooler: Pooler), }; module.exports = PooledClass; From d2039d7facee6cbd96111502b5539092b74d64de Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Wed, 21 Dec 2016 13:17:34 -0800 Subject: [PATCH 09/10] Improve error messages for invalid element types (#8612) (cherry picked from commit eca5b1d48e71218800156ce474ec79b990b09fbd) --- .../classic/element/ReactElementValidator.js | 32 +++++++--- .../__tests__/ReactElementValidator-test.js | 58 ++++++++++++------- .../ReactJSXElementValidator-test.js | 29 ++++++---- .../__tests__/ReactComponent-test.js | 23 +++++++- .../reconciler/instantiateReactComponent.js | 35 ++++++++--- 5 files changed, 127 insertions(+), 50 deletions(-) diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index 8c30884017..e7ad9d72a7 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -187,13 +187,31 @@ var ReactElementValidator = { // We warn in this case but don't throw. We expect the element creation to // succeed and there will likely be errors in render. if (!validType) { - warning( - false, - 'React.createElement: type should not be null, undefined, boolean, or ' + - 'number. It should be a string (for DOM elements) or a ReactClass ' + - '(for composite components).%s', - getDeclarationErrorAddendum() - ); + if ( + typeof type !== 'function' && + typeof type !== 'string' + ) { + var info = ''; + if ( + type === undefined || + typeof type === 'object' && + type !== null && + Object.keys(type).length === 0 + ) { + info += + ' You likely forgot to export your component from the file ' + + 'it\'s defined in.'; + } + info += getDeclarationErrorAddendum(); + warning( + false, + 'React.createElement: type is invalid -- expected a string (for ' + + 'built-in components) or a class/function (for composite ' + + 'components) but got: %s.%s', + type == null ? type : typeof type, + info, + ); + } } var element = ReactElement.createElement.apply(this, arguments); diff --git a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js index 5690e2125b..b36fa2f39c 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js @@ -289,35 +289,49 @@ describe('ReactElementValidator', () => { ); }); - it('gives a helpful error when passing null, undefined, boolean, or number', () => { + it('gives a helpful error when passing invalid types', () => { spyOn(console, 'error'); React.createElement(undefined); React.createElement(null); React.createElement(true); React.createElement(123); - expect(console.error.calls.count()).toBe(4); + React.createElement({x: 17}); + React.createElement({}); + expect(console.error.calls.count()).toBe(6); expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: React.createElement: type should not be null, undefined, ' + - 'boolean, or number. It should be a string (for DOM elements) or a ' + - 'ReactClass (for composite components).' + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: undefined. You likely forgot to export your ' + + 'component from the file it\'s defined in.' ); expect(console.error.calls.argsFor(1)[0]).toBe( - 'Warning: React.createElement: type should not be null, undefined, ' + - 'boolean, or number. It should be a string (for DOM elements) or a ' + - 'ReactClass (for composite components).' + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: null.' ); expect(console.error.calls.argsFor(2)[0]).toBe( - 'Warning: React.createElement: type should not be null, undefined, ' + - 'boolean, or number. It should be a string (for DOM elements) or a ' + - 'ReactClass (for composite components).' + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: boolean.' ); expect(console.error.calls.argsFor(3)[0]).toBe( - 'Warning: React.createElement: type should not be null, undefined, ' + - 'boolean, or number. It should be a string (for DOM elements) or a ' + - 'ReactClass (for composite components).' + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: number.' + ); + expect(console.error.calls.argsFor(4)[0]).toBe( + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: object.' + ); + expect(console.error.calls.argsFor(5)[0]).toBe( + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: object. You likely forgot to export your ' + + 'component from the file it\'s defined in.' ); React.createElement('div'); - expect(console.error.calls.count()).toBe(4); + expect(console.error.calls.count()).toBe(6); }); it('includes the owner name when passing null, undefined, boolean, or number', () => { @@ -336,10 +350,9 @@ describe('ReactElementValidator', () => { ); expect(console.error.calls.count()).toBe(1); expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: React.createElement: type should not be null, undefined, ' + - 'boolean, or number. It should be a string (for DOM elements) or a ' + - 'ReactClass (for composite components). Check the render method of ' + - '`ParentComp`.' + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: null. Check the render method of `ParentComp`.' ); }); @@ -537,9 +550,10 @@ describe('ReactElementValidator', () => { void {[
]}; expect(console.error.calls.count()).toBe(1); expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: React.createElement: type should not be null, undefined, ' + - 'boolean, or number. It should be a string (for DOM elements) or a ' + - 'ReactClass (for composite components).' + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: undefined. You likely forgot to export your ' + + 'component from the file it\'s defined in.' ); }); diff --git a/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js b/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js index 71fbed5bd3..00f3650217 100644 --- a/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js +++ b/src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js @@ -218,21 +218,26 @@ describe('ReactJSXElementValidator', () => { void ; void ; expect(console.error.calls.count()).toBe(4); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'type should not be null, undefined, boolean, or number. It should be ' + - 'a string (for DOM elements) or a ReactClass (for composite components).' + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: undefined. You likely forgot to export your ' + + 'component from the file it\'s defined in.' ); - expect(console.error.calls.argsFor(1)[0]).toContain( - 'type should not be null, undefined, boolean, or number. It should be ' + - 'a string (for DOM elements) or a ReactClass (for composite components).' + expect(console.error.calls.argsFor(1)[0]).toBe( + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: null.' ); - expect(console.error.calls.argsFor(2)[0]).toContain( - 'type should not be null, undefined, boolean, or number. It should be ' + - 'a string (for DOM elements) or a ReactClass (for composite components).' + expect(console.error.calls.argsFor(2)[0]).toBe( + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: boolean.' ); - expect(console.error.calls.argsFor(3)[0]).toContain( - 'type should not be null, undefined, boolean, or number. It should be ' + - 'a string (for DOM elements) or a ReactClass (for composite components).' + expect(console.error.calls.argsFor(3)[0]).toBe( + 'Warning: React.createElement: type is invalid -- expected a string ' + + '(for built-in components) or a class/function (for composite ' + + 'components) but got: number.' ); void
; expect(console.error.calls.count()).toBe(4); diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js index 769b2bafcc..28c914152b 100644 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js +++ b/src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js @@ -327,7 +327,9 @@ describe('ReactComponent', () => { var X = undefined; expect(() => ReactTestUtils.renderIntoDocument()).toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + - 'or a class/function (for composite components) but got: undefined.' + 'or a class/function (for composite components) but got: undefined. ' + + 'You likely forgot to export your component from the file it\'s ' + + 'defined in.' ); var Y = null; @@ -340,4 +342,23 @@ describe('ReactComponent', () => { expect(console.error.calls.count()).toBe(2); }); + it('includes owner name in the error about badly-typed elements', () => { + spyOn(console, 'error'); + + function Foo() { + var X = undefined; + return ; + } + + expect(() => ReactTestUtils.renderIntoDocument()).toThrowError( + 'Element type is invalid: expected a string (for built-in components) ' + + 'or a class/function (for composite components) but got: undefined. ' + + 'You likely forgot to export your component from the file it\'s ' + + 'defined in. Check the render method of `Foo`.' + ); + + // One warning for each element creation + expect(console.error.calls.count()).toBe(1); + }); + }); diff --git a/src/renderers/shared/stack/reconciler/instantiateReactComponent.js b/src/renderers/shared/stack/reconciler/instantiateReactComponent.js index 202768d8bb..d1b5c23262 100644 --- a/src/renderers/shared/stack/reconciler/instantiateReactComponent.js +++ b/src/renderers/shared/stack/reconciler/instantiateReactComponent.js @@ -72,14 +72,33 @@ function instantiateReactComponent(node, shouldHaveDebugID) { instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { var element = node; - invariant( - element && (typeof element.type === 'function' || - typeof element.type === 'string'), - 'Element type is invalid: expected a string (for built-in components) ' + - 'or a class/function (for composite components) but got: %s.%s', - element.type == null ? element.type : typeof element.type, - getDeclarationErrorAddendum(element._owner) - ); + var type = element.type; + if ( + typeof type !== 'function' && + typeof type !== 'string' + ) { + var info = ''; + if (__DEV__) { + if ( + type === undefined || + typeof type === 'object' && + type !== null && + Object.keys(type).length === 0 + ) { + info += + ' You likely forgot to export your component from the file ' + + 'it\'s defined in.'; + } + } + info += getDeclarationErrorAddendum(element._owner); + invariant( + false, + 'Element type is invalid: expected a string (for built-in components) ' + + 'or a class/function (for composite components) but got: %s.%s', + type == null ? type : typeof type, + info, + ); + } // Special case string values if (typeof element.type === 'string') { From 4294a7c908d06de5f04d7b78a3e32c5721702ef6 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 5 Jan 2017 13:16:08 -0800 Subject: [PATCH 10/10] Fix AMD and Brunch issues (#8686) * Add manual build fixtures * Inject ReactDOM into ReactWithAddons from ReactWithAddons We used to read ReactDOM as a global inside ReactAddonsDOMDependenciesUMDShim. This didn't work in AMD environments such as RequireJS and SystemJS. Instead, I changed it so that ReactDOM gets injected into ReactWithAddons by ReactDOM itself. This way we don't have to try to require it (which wouldn't work because AMD doesn't handle circular dependencies well). This means you have to load ReactDOM first before using ReactDOM-dependent addons, but this was already the case before. This commit makes all build fixtures pass. * Memoize ReactDOM to avoid going into require on every access * Add Brunch fixture * Inline requires to work around Brunch bug See #8556 and https://github.com/brunch/brunch/issues/1591#issuecomment-270742503 for context. This appears to be a Brunch bug but we can keep a temporary fix until the next major. (cherry picked from commit ca2c71c0c5e8bb9722b58b0309398c69d1b9642d) --- fixtures/README.md | 50 ++++++++++++++++ fixtures/browserify/.gitignore | 1 + fixtures/browserify/index.html | 16 ++++++ fixtures/browserify/input.js | 16 ++++++ fixtures/browserify/package.json | 10 ++++ fixtures/brunch/.gitignore | 2 + fixtures/brunch/app/initialize.js | 16 ++++++ fixtures/brunch/config.js | 10 ++++ fixtures/brunch/index.html | 17 ++++++ fixtures/brunch/input.js | 16 ++++++ fixtures/brunch/package.json | 10 ++++ fixtures/build-all.js | 30 ++++++++++ fixtures/globals.html | 32 +++++++++++ fixtures/requirejs.html | 40 +++++++++++++ fixtures/rjs/.gitignore | 1 + fixtures/rjs/config.js | 10 ++++ fixtures/rjs/index.html | 17 ++++++ fixtures/rjs/input.js | 15 +++++ fixtures/rjs/package.json | 10 ++++ fixtures/systemjs-builder/.gitignore | 1 + fixtures/systemjs-builder/build.js | 12 ++++ fixtures/systemjs-builder/config.js | 6 ++ fixtures/systemjs-builder/index.html | 16 ++++++ fixtures/systemjs-builder/input.js | 16 ++++++ fixtures/systemjs-builder/package.json | 10 ++++ fixtures/systemjs.html | 46 +++++++++++++++ fixtures/webpack-alias/.gitignore | 1 + fixtures/webpack-alias/config.js | 13 +++++ fixtures/webpack-alias/index.html | 16 ++++++ fixtures/webpack-alias/input.js | 16 ++++++ fixtures/webpack-alias/package.json | 10 ++++ fixtures/webpack/.gitignore | 1 + fixtures/webpack/config.js | 9 +++ fixtures/webpack/index.html | 16 ++++++ fixtures/webpack/input.js | 16 ++++++ fixtures/webpack/package.json | 10 ++++ src/addons/ReactAddonsDOMDependencies.js | 15 ++--- .../transitions/ReactTransitionGroup.js | 57 ++++--------------- .../__tests__/ReactTransitionGroup-test.js | 12 +--- src/umd/ReactDOMUMDEntry.js | 28 ++++----- src/umd/ReactWithAddonsUMDEntry.js | 1 + .../ReactAddonsDOMDependenciesUMDShim.js | 24 ++++---- 42 files changed, 586 insertions(+), 85 deletions(-) create mode 100644 fixtures/README.md create mode 100644 fixtures/browserify/.gitignore create mode 100644 fixtures/browserify/index.html create mode 100644 fixtures/browserify/input.js create mode 100644 fixtures/browserify/package.json create mode 100644 fixtures/brunch/.gitignore create mode 100644 fixtures/brunch/app/initialize.js create mode 100644 fixtures/brunch/config.js create mode 100644 fixtures/brunch/index.html create mode 100644 fixtures/brunch/input.js create mode 100644 fixtures/brunch/package.json create mode 100644 fixtures/build-all.js create mode 100644 fixtures/globals.html create mode 100644 fixtures/requirejs.html create mode 100644 fixtures/rjs/.gitignore create mode 100644 fixtures/rjs/config.js create mode 100644 fixtures/rjs/index.html create mode 100644 fixtures/rjs/input.js create mode 100644 fixtures/rjs/package.json create mode 100644 fixtures/systemjs-builder/.gitignore create mode 100644 fixtures/systemjs-builder/build.js create mode 100644 fixtures/systemjs-builder/config.js create mode 100644 fixtures/systemjs-builder/index.html create mode 100644 fixtures/systemjs-builder/input.js create mode 100644 fixtures/systemjs-builder/package.json create mode 100644 fixtures/systemjs.html create mode 100644 fixtures/webpack-alias/.gitignore create mode 100644 fixtures/webpack-alias/config.js create mode 100644 fixtures/webpack-alias/index.html create mode 100644 fixtures/webpack-alias/input.js create mode 100644 fixtures/webpack-alias/package.json create mode 100644 fixtures/webpack/.gitignore create mode 100644 fixtures/webpack/config.js create mode 100644 fixtures/webpack/index.html create mode 100644 fixtures/webpack/input.js create mode 100644 fixtures/webpack/package.json diff --git a/fixtures/README.md b/fixtures/README.md new file mode 100644 index 0000000000..0c9646ce4d --- /dev/null +++ b/fixtures/README.md @@ -0,0 +1,50 @@ +# Manual Testing Fixtures + +This folder exists for **React contributors** only. +If you use React you don't need to worry about it. + +These fixtures verify that the built React distributions are usable in different environments. +**They are not running automatically.** (At least not yet, feel free to contribute to automate them.) + +Run them when you make changes to how we package React, ReactDOM, and addons. + +## How to Run + +First, build React and the fixtures: + +``` +cd react +npm run build + +cd fixtures +node build-all.js +``` + +Then run a local server at the root of the repo, e.g. + +``` +npm i -g pushstate-server +cd .. +pushstate-server . +``` + +(Too complicated? Send a PR to simplify this :-). + +Then open the corresponding URLs, for example: + +``` +open http://localhost:9000/fixtures/globals.html +open http://localhost:9000/fixtures/requirejs.html +open http://localhost:9000/fixtures/systemjs.html +open http://localhost:9000/fixtures/browserify/index.html +open http://localhost:9000/fixtures/brunch/index.html +open http://localhost:9000/fixtures/rjs/index.html +open http://localhost:9000/fixtures/systemjs-builder/index.html +open http://localhost:9000/fixtures/webpack/index.html +open http://localhost:9000/fixtures/webpack-alias/index.html +``` + +You should see two things: + +* "Hello World" fading in with an animation. +* No errors in the console. diff --git a/fixtures/browserify/.gitignore b/fixtures/browserify/.gitignore new file mode 100644 index 0000000000..66b00e6a05 --- /dev/null +++ b/fixtures/browserify/.gitignore @@ -0,0 +1 @@ +output.js diff --git a/fixtures/browserify/index.html b/fixtures/browserify/index.html new file mode 100644 index 0000000000..d2b798e524 --- /dev/null +++ b/fixtures/browserify/index.html @@ -0,0 +1,16 @@ + + + +
+ + + \ No newline at end of file diff --git a/fixtures/browserify/input.js b/fixtures/browserify/input.js new file mode 100644 index 0000000000..da6cc5d0ff --- /dev/null +++ b/fixtures/browserify/input.js @@ -0,0 +1,16 @@ +var React = require('react'); +var CSSTransitionGroup = require('react-addons-css-transition-group'); +var ReactDOM = require('react-dom'); + +ReactDOM.render( + React.createElement(CSSTransitionGroup, { + transitionName: 'example', + transitionAppear: true, + transitionAppearTimeout: 500, + transitionEnterTimeout: 0, + transitionLeaveTimeout: 0, + }, React.createElement('h1', null, + 'Hello World!' + )), + document.getElementById('container') +); diff --git a/fixtures/browserify/package.json b/fixtures/browserify/package.json new file mode 100644 index 0000000000..dbcf1dd834 --- /dev/null +++ b/fixtures/browserify/package.json @@ -0,0 +1,10 @@ +{ + "name": "webpack-test", + "private": true, + "dependencies": { + "browserify": "^13.3.0" + }, + "scripts": { + "build": "rm -f output.js && NODE_PATH=../../build/packages browserify ./input.js -o output.js" + } +} diff --git a/fixtures/brunch/.gitignore b/fixtures/brunch/.gitignore new file mode 100644 index 0000000000..b8d1dfad58 --- /dev/null +++ b/fixtures/brunch/.gitignore @@ -0,0 +1,2 @@ +output.js +output.js.map \ No newline at end of file diff --git a/fixtures/brunch/app/initialize.js b/fixtures/brunch/app/initialize.js new file mode 100644 index 0000000000..da6cc5d0ff --- /dev/null +++ b/fixtures/brunch/app/initialize.js @@ -0,0 +1,16 @@ +var React = require('react'); +var CSSTransitionGroup = require('react-addons-css-transition-group'); +var ReactDOM = require('react-dom'); + +ReactDOM.render( + React.createElement(CSSTransitionGroup, { + transitionName: 'example', + transitionAppear: true, + transitionAppearTimeout: 500, + transitionEnterTimeout: 0, + transitionLeaveTimeout: 0, + }, React.createElement('h1', null, + 'Hello World!' + )), + document.getElementById('container') +); diff --git a/fixtures/brunch/config.js b/fixtures/brunch/config.js new file mode 100644 index 0000000000..ba12fc72c2 --- /dev/null +++ b/fixtures/brunch/config.js @@ -0,0 +1,10 @@ +exports.config = { + paths: { + public: '.', + }, + files: { + javascripts: { + joinTo: 'output.js', + }, + }, +}; diff --git a/fixtures/brunch/index.html b/fixtures/brunch/index.html new file mode 100644 index 0000000000..859d98d0f2 --- /dev/null +++ b/fixtures/brunch/index.html @@ -0,0 +1,17 @@ + + + +
+ + + + \ No newline at end of file diff --git a/fixtures/brunch/input.js b/fixtures/brunch/input.js new file mode 100644 index 0000000000..da6cc5d0ff --- /dev/null +++ b/fixtures/brunch/input.js @@ -0,0 +1,16 @@ +var React = require('react'); +var CSSTransitionGroup = require('react-addons-css-transition-group'); +var ReactDOM = require('react-dom'); + +ReactDOM.render( + React.createElement(CSSTransitionGroup, { + transitionName: 'example', + transitionAppear: true, + transitionAppearTimeout: 500, + transitionEnterTimeout: 0, + transitionLeaveTimeout: 0, + }, React.createElement('h1', null, + 'Hello World!' + )), + document.getElementById('container') +); diff --git a/fixtures/brunch/package.json b/fixtures/brunch/package.json new file mode 100644 index 0000000000..d00bcc0ef0 --- /dev/null +++ b/fixtures/brunch/package.json @@ -0,0 +1,10 @@ +{ + "name": "brunch-test", + "devDependencies": { + "brunch": "^2.9.1", + "javascript-brunch": "^2.0.0" + }, + "scripts": { + "build": "rm -rf public && ln -fs ../../../build/packages/react node_modules/react && ln -fs ../../../build/packages/react-dom node_modules/react-dom && ln -fs ../../../build/packages/react-addons-css-transition-group node_modules/react-addons-css-transition-group && brunch build" + } +} \ No newline at end of file diff --git a/fixtures/build-all.js b/fixtures/build-all.js new file mode 100644 index 0000000000..b5620c0fa3 --- /dev/null +++ b/fixtures/build-all.js @@ -0,0 +1,30 @@ +var fs = require('fs'); +var path = require('path'); +var { spawnSync } = require('child_process'); + +var fixtureDirs = fs.readdirSync(__dirname).filter((file) => { + return fs.statSync(path.join(__dirname, file)).isDirectory(); +}); + +var cmdArgs = [ + {cmd: 'npm', args: ['install']}, + {cmd: 'npm', args: ['run', 'build']}, +]; + +for (const dir of fixtureDirs) { + for (const cmdArg of cmdArgs) { + const opts = { + cwd: path.join(__dirname, dir), + stdio: 'inherit', + }; + let result = spawnSync(cmdArg.cmd, cmdArg.args, opts); + if (result.status !== 0) { + throw new Error('Failed to build fixtures.'); + } + } +} + +console.log('-------------------------'); +console.log('All fixtures were built!'); +console.log('Now make sure to open each HTML file in this directory and each index.html in subdirectories.'); +console.log('-------------------------'); diff --git a/fixtures/globals.html b/fixtures/globals.html new file mode 100644 index 0000000000..c3a7da3c59 --- /dev/null +++ b/fixtures/globals.html @@ -0,0 +1,32 @@ + + + + + +
+ + + \ No newline at end of file diff --git a/fixtures/requirejs.html b/fixtures/requirejs.html new file mode 100644 index 0000000000..c2ff2338a3 --- /dev/null +++ b/fixtures/requirejs.html @@ -0,0 +1,40 @@ + + + + +
+ + + \ No newline at end of file diff --git a/fixtures/rjs/.gitignore b/fixtures/rjs/.gitignore new file mode 100644 index 0000000000..fa213fe698 --- /dev/null +++ b/fixtures/rjs/.gitignore @@ -0,0 +1 @@ +output.js \ No newline at end of file diff --git a/fixtures/rjs/config.js b/fixtures/rjs/config.js new file mode 100644 index 0000000000..f15dc11003 --- /dev/null +++ b/fixtures/rjs/config.js @@ -0,0 +1,10 @@ +module.exports = { + baseUrl: '.', + name: 'input', + out: 'output.js', + optimize: 'none', + paths: { + react: '../../build/react-with-addons', + 'react-dom': '../../build/react-dom', + }, +}; diff --git a/fixtures/rjs/index.html b/fixtures/rjs/index.html new file mode 100644 index 0000000000..24995379a1 --- /dev/null +++ b/fixtures/rjs/index.html @@ -0,0 +1,17 @@ + + + +
+ + + + \ No newline at end of file diff --git a/fixtures/rjs/input.js b/fixtures/rjs/input.js new file mode 100644 index 0000000000..2b0deee96c --- /dev/null +++ b/fixtures/rjs/input.js @@ -0,0 +1,15 @@ +require(['react', 'react-dom'], function(React, ReactDOM) { + var CSSTransitionGroup = React.addons.CSSTransitionGroup; + ReactDOM.render( + React.createElement(CSSTransitionGroup, { + transitionName: 'example', + transitionAppear: true, + transitionAppearTimeout: 500, + transitionEnterTimeout: 0, + transitionLeaveTimeout: 0, + }, React.createElement('h1', null, + 'Hello World!' + )), + document.getElementById('container') + ); +}); diff --git a/fixtures/rjs/package.json b/fixtures/rjs/package.json new file mode 100644 index 0000000000..bc94a37011 --- /dev/null +++ b/fixtures/rjs/package.json @@ -0,0 +1,10 @@ +{ + "name": "rjs-test", + "private": true, + "dependencies": { + "requirejs": "^2.3.2" + }, + "scripts": { + "build": "rm -f output.js && r.js -o config.js" + } +} diff --git a/fixtures/systemjs-builder/.gitignore b/fixtures/systemjs-builder/.gitignore new file mode 100644 index 0000000000..fa213fe698 --- /dev/null +++ b/fixtures/systemjs-builder/.gitignore @@ -0,0 +1 @@ +output.js \ No newline at end of file diff --git a/fixtures/systemjs-builder/build.js b/fixtures/systemjs-builder/build.js new file mode 100644 index 0000000000..d476c09a64 --- /dev/null +++ b/fixtures/systemjs-builder/build.js @@ -0,0 +1,12 @@ +var Builder = require('systemjs-builder'); + +var builder = new Builder('/', './config.js'); +builder + .buildStatic('./input.js', './output.js') + .then(function() { + console.log('Build complete'); + }) + .catch(function(err) { + console.log('Build error'); + console.log(err); + }); diff --git a/fixtures/systemjs-builder/config.js b/fixtures/systemjs-builder/config.js new file mode 100644 index 0000000000..baed2db8c6 --- /dev/null +++ b/fixtures/systemjs-builder/config.js @@ -0,0 +1,6 @@ +System.config({ + paths: { + react: '../../build/react-with-addons.js', + 'react-dom': '../../build/react-dom.js', + }, +}); diff --git a/fixtures/systemjs-builder/index.html b/fixtures/systemjs-builder/index.html new file mode 100644 index 0000000000..d2b798e524 --- /dev/null +++ b/fixtures/systemjs-builder/index.html @@ -0,0 +1,16 @@ + + + +
+ + + \ No newline at end of file diff --git a/fixtures/systemjs-builder/input.js b/fixtures/systemjs-builder/input.js new file mode 100644 index 0000000000..27d5c10086 --- /dev/null +++ b/fixtures/systemjs-builder/input.js @@ -0,0 +1,16 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +var CSSTransitionGroup = React.addons.CSSTransitionGroup; +ReactDOM.render( + React.createElement(CSSTransitionGroup, { + transitionName: 'example', + transitionAppear: true, + transitionAppearTimeout: 500, + transitionEnterTimeout: 0, + transitionLeaveTimeout: 0, + }, React.createElement('h1', null, + 'Hello World!' + )), + document.getElementById('container') +); diff --git a/fixtures/systemjs-builder/package.json b/fixtures/systemjs-builder/package.json new file mode 100644 index 0000000000..b4c6acdcca --- /dev/null +++ b/fixtures/systemjs-builder/package.json @@ -0,0 +1,10 @@ +{ + "name": "systemjs-builder-test", + "private": true, + "dependencies": { + "systemjs-builder": "^0.15.34" + }, + "scripts": { + "build": "rm -f output.js && node build.js" + } +} diff --git a/fixtures/systemjs.html b/fixtures/systemjs.html new file mode 100644 index 0000000000..9c4430791c --- /dev/null +++ b/fixtures/systemjs.html @@ -0,0 +1,46 @@ + + + + +
+ + + \ No newline at end of file diff --git a/fixtures/webpack-alias/.gitignore b/fixtures/webpack-alias/.gitignore new file mode 100644 index 0000000000..fa213fe698 --- /dev/null +++ b/fixtures/webpack-alias/.gitignore @@ -0,0 +1 @@ +output.js \ No newline at end of file diff --git a/fixtures/webpack-alias/config.js b/fixtures/webpack-alias/config.js new file mode 100644 index 0000000000..dcc0c3a907 --- /dev/null +++ b/fixtures/webpack-alias/config.js @@ -0,0 +1,13 @@ +module.exports = { + entry: './input', + output: { + filename: 'output.js', + }, + resolve: { + root: '../../build/packages', + alias: { + 'react': 'react/dist/react-with-addons', + 'react-dom': 'react-dom/dist/react-dom', + }, + }, +}; diff --git a/fixtures/webpack-alias/index.html b/fixtures/webpack-alias/index.html new file mode 100644 index 0000000000..d2b798e524 --- /dev/null +++ b/fixtures/webpack-alias/index.html @@ -0,0 +1,16 @@ + + + +
+ + + \ No newline at end of file diff --git a/fixtures/webpack-alias/input.js b/fixtures/webpack-alias/input.js new file mode 100644 index 0000000000..f1e2d79e23 --- /dev/null +++ b/fixtures/webpack-alias/input.js @@ -0,0 +1,16 @@ +var React = require('react'); +var ReactDOM = require('react-dom'); + +var CSSTransitionGroup = React.addons.CSSTransitionGroup; +ReactDOM.render( + React.createElement(CSSTransitionGroup, { + transitionName: 'example', + transitionAppear: true, + transitionAppearTimeout: 500, + transitionEnterTimeout: 0, + transitionLeaveTimeout: 0, + }, React.createElement('h1', null, + 'Hello World!' + )), + document.getElementById('container') +); diff --git a/fixtures/webpack-alias/package.json b/fixtures/webpack-alias/package.json new file mode 100644 index 0000000000..8ea2da478b --- /dev/null +++ b/fixtures/webpack-alias/package.json @@ -0,0 +1,10 @@ +{ + "name": "webpack-test", + "private": true, + "dependencies": { + "webpack": "^1.14.0" + }, + "scripts": { + "build": "rm -f output.js && webpack --config config.js" + } +} diff --git a/fixtures/webpack/.gitignore b/fixtures/webpack/.gitignore new file mode 100644 index 0000000000..fa213fe698 --- /dev/null +++ b/fixtures/webpack/.gitignore @@ -0,0 +1 @@ +output.js \ No newline at end of file diff --git a/fixtures/webpack/config.js b/fixtures/webpack/config.js new file mode 100644 index 0000000000..685cac24ee --- /dev/null +++ b/fixtures/webpack/config.js @@ -0,0 +1,9 @@ +module.exports = { + entry: './input', + output: { + filename: 'output.js', + }, + resolve: { + root: '../../build/packages', + }, +}; diff --git a/fixtures/webpack/index.html b/fixtures/webpack/index.html new file mode 100644 index 0000000000..d2b798e524 --- /dev/null +++ b/fixtures/webpack/index.html @@ -0,0 +1,16 @@ + + + +
+ + + \ No newline at end of file diff --git a/fixtures/webpack/input.js b/fixtures/webpack/input.js new file mode 100644 index 0000000000..da6cc5d0ff --- /dev/null +++ b/fixtures/webpack/input.js @@ -0,0 +1,16 @@ +var React = require('react'); +var CSSTransitionGroup = require('react-addons-css-transition-group'); +var ReactDOM = require('react-dom'); + +ReactDOM.render( + React.createElement(CSSTransitionGroup, { + transitionName: 'example', + transitionAppear: true, + transitionAppearTimeout: 500, + transitionEnterTimeout: 0, + transitionLeaveTimeout: 0, + }, React.createElement('h1', null, + 'Hello World!' + )), + document.getElementById('container') +); diff --git a/fixtures/webpack/package.json b/fixtures/webpack/package.json new file mode 100644 index 0000000000..8ea2da478b --- /dev/null +++ b/fixtures/webpack/package.json @@ -0,0 +1,10 @@ +{ + "name": "webpack-test", + "private": true, + "dependencies": { + "webpack": "^1.14.0" + }, + "scripts": { + "build": "rm -f output.js && webpack --config config.js" + } +} diff --git a/src/addons/ReactAddonsDOMDependencies.js b/src/addons/ReactAddonsDOMDependencies.js index e0b5eb45a5..70856ec6c5 100644 --- a/src/addons/ReactAddonsDOMDependencies.js +++ b/src/addons/ReactAddonsDOMDependencies.js @@ -12,25 +12,26 @@ 'use strict'; var ReactDOM = require('ReactDOM'); -var ReactInstanceMap = require('ReactInstanceMap'); exports.getReactDOM = function() { return ReactDOM; }; -exports.getReactInstanceMap = function() { - return ReactInstanceMap; -}; - if (__DEV__) { - var ReactPerf = require('ReactPerf'); - var ReactTestUtils = require('ReactTestUtils'); + var ReactPerf; + var ReactTestUtils; exports.getReactPerf = function() { + if (!ReactPerf) { + ReactPerf = require('ReactPerf'); + } return ReactPerf; }; exports.getReactTestUtils = function() { + if (!ReactTestUtils) { + ReactTestUtils = require('ReactTestUtils'); + } return ReactTestUtils; }; } diff --git a/src/addons/transitions/ReactTransitionGroup.js b/src/addons/transitions/ReactTransitionGroup.js index 001b06fb9d..62372801fa 100644 --- a/src/addons/transitions/ReactTransitionGroup.js +++ b/src/addons/transitions/ReactTransitionGroup.js @@ -12,7 +12,6 @@ 'use strict'; var React = require('React'); -var ReactAddonsDOMDependencies = require('ReactAddonsDOMDependencies'); var ReactTransitionChildMapping = require('ReactTransitionChildMapping'); var emptyFunction = require('emptyFunction'); @@ -56,17 +55,9 @@ class ReactTransitionGroup extends React.Component { } componentWillReceiveProps(nextProps) { - var nextChildMapping; - if (__DEV__) { - nextChildMapping = ReactTransitionChildMapping.getChildMapping( - nextProps.children, - ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID - ); - } else { - nextChildMapping = ReactTransitionChildMapping.getChildMapping( - nextProps.children - ); - } + var nextChildMapping = ReactTransitionChildMapping.getChildMapping( + nextProps.children + ); var prevChildMapping = this.state.children; this.setState({ @@ -129,17 +120,9 @@ class ReactTransitionGroup extends React.Component { delete this.currentlyTransitioningKeys[key]; - var currentChildMapping; - if (__DEV__) { - currentChildMapping = ReactTransitionChildMapping.getChildMapping( - this.props.children, - ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID - ); - } else { - currentChildMapping = ReactTransitionChildMapping.getChildMapping( - this.props.children - ); - } + var currentChildMapping = ReactTransitionChildMapping.getChildMapping( + this.props.children + ); if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) { // This was removed before it had fully appeared. Remove it. @@ -169,17 +152,9 @@ class ReactTransitionGroup extends React.Component { delete this.currentlyTransitioningKeys[key]; - var currentChildMapping; - if (__DEV__) { - currentChildMapping = ReactTransitionChildMapping.getChildMapping( - this.props.children, - ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID - ); - } else { - currentChildMapping = ReactTransitionChildMapping.getChildMapping( - this.props.children - ); - } + var currentChildMapping = ReactTransitionChildMapping.getChildMapping( + this.props.children + ); if (!currentChildMapping || !currentChildMapping.hasOwnProperty(key)) { // This was removed before it had fully entered. Remove it. @@ -210,17 +185,9 @@ class ReactTransitionGroup extends React.Component { delete this.currentlyTransitioningKeys[key]; - var currentChildMapping; - if (__DEV__) { - currentChildMapping = ReactTransitionChildMapping.getChildMapping( - this.props.children, - ReactAddonsDOMDependencies.getReactInstanceMap().get(this)._debugID - ); - } else { - currentChildMapping = ReactTransitionChildMapping.getChildMapping( - this.props.children - ); - } + var currentChildMapping = ReactTransitionChildMapping.getChildMapping( + this.props.children + ); if (currentChildMapping && currentChildMapping.hasOwnProperty(key)) { // This entered again before it fully left. Add it again. diff --git a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js index 90c0e01900..25e6dfdcd1 100644 --- a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js +++ b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js @@ -20,10 +20,6 @@ var ReactTransitionGroup; describe('ReactTransitionGroup', () => { var container; - function normalizeCodeLocInfo(str) { - return str.replace(/\(at .+?:\d+\)/g, '(at **)'); - } - beforeEach(() => { React = require('React'); ReactDOM = require('ReactDOM'); @@ -296,7 +292,7 @@ describe('ReactTransitionGroup', () => { ]); }); - it('should warn for duplicated keys with component stack info', () => { + it('should warn for duplicated keys', () => { spyOn(console, 'error'); class Component extends React.Component { @@ -315,13 +311,11 @@ describe('ReactTransitionGroup', () => { 'Child keys must be unique; when two children share a key, ' + 'only the first child will be used.' ); - expect(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( + expect(console.error.calls.argsFor(1)[0]).toBe( 'Warning: flattenChildren(...): ' + 'Encountered two children with the same key, `1`. ' + 'Child keys must be unique; when two children share a key, ' + - 'only the first child will be used.\n' + - ' in ReactTransitionGroup (at **)\n' + - ' in Component (at **)' + 'only the first child will be used.' ); }); }); diff --git a/src/umd/ReactDOMUMDEntry.js b/src/umd/ReactDOMUMDEntry.js index 59daf4bef9..03a0318a10 100644 --- a/src/umd/ReactDOMUMDEntry.js +++ b/src/umd/ReactDOMUMDEntry.js @@ -11,24 +11,24 @@ 'use strict'; +var React = require('React'); var ReactDOM = require('ReactDOM'); -var ReactDOMUMDEntry = Object.assign({ - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { - ReactInstanceMap: require('ReactInstanceMap'), - }, -}, ReactDOM); +var ReactDOMUMDEntry = ReactDOM; if (__DEV__) { - Object.assign( - ReactDOMUMDEntry.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, - { - // ReactPerf and ReactTestUtils currently only work with the DOM renderer - // so we expose them from here, but only in DEV mode. - ReactPerf: require('ReactPerf'), - ReactTestUtils: require('ReactTestUtils'), - } - ); + ReactDOMUMDEntry.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { + // ReactPerf and ReactTestUtils currently only work with the DOM renderer + // so we expose them from here, but only in DEV mode. + ReactPerf: require('ReactPerf'), + ReactTestUtils: require('ReactTestUtils'), + }; +} + +// Inject ReactDOM into React for the addons UMD build that depends on ReactDOM (TransitionGroup). +// We can remove this after we deprecate and remove the addons UMD build. +if (React.addons) { + React.__SECRET_INJECTED_REACT_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactDOMUMDEntry; } module.exports = ReactDOMUMDEntry; diff --git a/src/umd/ReactWithAddonsUMDEntry.js b/src/umd/ReactWithAddonsUMDEntry.js index ad05b565c9..c59e62e731 100644 --- a/src/umd/ReactWithAddonsUMDEntry.js +++ b/src/umd/ReactWithAddonsUMDEntry.js @@ -15,6 +15,7 @@ var ReactWithAddons = require('ReactWithAddons'); // `version` will be added here by the React module. var ReactWithAddonsUMDEntry = Object.assign({ + __SECRET_INJECTED_REACT_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: null, // Will be injected by ReactDOM UMD build. __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { ReactCurrentOwner: require('ReactCurrentOwner'), }, diff --git a/src/umd/shims/ReactAddonsDOMDependenciesUMDShim.js b/src/umd/shims/ReactAddonsDOMDependenciesUMDShim.js index 2aa7b886b5..f4c06de045 100644 --- a/src/umd/shims/ReactAddonsDOMDependenciesUMDShim.js +++ b/src/umd/shims/ReactAddonsDOMDependenciesUMDShim.js @@ -9,24 +9,28 @@ * @providesModule ReactAddonsDOMDependenciesUMDShim */ -/* globals ReactDOM */ - 'use strict'; -exports.getReactDOM = function() { - return ReactDOM; -}; +var ReactDOM; -exports.getReactInstanceMap = function() { - return ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactInstanceMap; -}; +function getReactDOM() { + if (!ReactDOM) { + // This is safe to use because current module only exists in the addons build: + var ReactWithAddonsUMDEntry = require('ReactWithAddonsUMDEntry'); + // This is injected by the ReactDOM UMD build: + ReactDOM = ReactWithAddonsUMDEntry.__SECRET_INJECTED_REACT_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; + } + return ReactDOM; +} + +exports.getReactDOM = getReactDOM; if (__DEV__) { exports.getReactPerf = function() { - return ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactPerf; + return getReactDOM().__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactPerf; }; exports.getReactTestUtils = function() { - return ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactTestUtils; + return getReactDOM().__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactTestUtils; }; }