@samitouri / QOS-React / commits / d2ff462b79

Pass shouldHaveDebugID flag to instantiateComponent (#7193)

* Add failing tests for #7187 and #7190 * Pass shouldHaveDebugID flag to instantiateComponent This allows us to remove a hack that was added in #6770 and caused #7187 and #7190. * Move logic for choosing whether to use debugID outside instantiate

Dan Abramov committed Jul 6, 2016 at 00:22 UTC d2ff462b79937ec3016dcba81d91d93a4a015c78
11 files changed +159 -137
src/isomorphic/devtools/ReactComponentTreeDevtool.js
-5
@@ -32,10 +32,6 @@ function updateTree(id, update) {
32 isMounted: false,
33 updateCount: 0,
34 };
35 - // TODO: We need to do this awkward dance because TopLevelWrapper "never
36 - // gets mounted" but its display name gets set in instantiateReactComponent
37 - // before its debug ID is set to 0.
38 - unmountedIDs[id] = true;
35 }
36 update(tree[id]);
37 }
@@ -148,7 +144,6 @@ var ReactComponentTreeDevtool = {
144
145 onMountComponent(id) {
146 updateTree(id, item => item.isMounted = true);
151 - delete unmountedIDs[id];
147 },
148
149 onMountRootComponent(id) {
src/renderers/dom/client/ReactMount.js
+1 -7
@@ -342,13 +342,7 @@ var ReactMount = {
342 );
343
344 ReactBrowserEventEmitter.ensureScrollValueMonitoring();
345 - var componentInstance = instantiateReactComponent(nextElement);
346 -
347 - if (__DEV__) {
348 - // Mute future events from the top level wrapper.
349 - // It is an implementation detail that devtools should not know about.
350 - componentInstance._debugID = 0;
351 - }
345 + var componentInstance = instantiateReactComponent(nextElement, false);
346
347 // The initial render is synchronous but any updates that happen during
348 // rendering, in componentWillMount or componentDidMount, will be batched
src/renderers/dom/server/ReactServerRendering.js
+1 -1
@@ -40,7 +40,7 @@ function renderToStringImpl(element, makeStaticMarkup) {
40 if (__DEV__) {
41 ReactInstrumentation.debugTool.onBeginFlush();
42 }
43 - var componentInstance = instantiateReactComponent(element);
43 + var componentInstance = instantiateReactComponent(element, true);
44 var markup = ReactReconciler.mountComponent(
45 componentInstance,
46 transaction,
src/renderers/native/ReactNativeMount.js
+2 -8
@@ -135,17 +135,11 @@ var ReactNativeMount = {
135
136 ReactNativeTagHandles.assertRootTag(containerTag);
137
138 - var instance = instantiateReactComponent(nextWrappedElement);
138 + var instance = instantiateReactComponent(nextWrappedElement, false);
139 ReactNativeMount._instancesByContainerID[containerTag] = instance;
140
141 if (__DEV__) {
142 - // Mute future events from the top level wrapper.
143 - // It is an implementation detail that devtools should not know about.
144 - instance._debugID = 0;
145 -
146 - if (__DEV__) {
147 - ReactInstrumentation.debugTool.onBeginFlush();
148 - }
142 + ReactInstrumentation.debugTool.onBeginFlush();
143 }
144
145 // The initial render is synchronous but any updates that happen during
src/renderers/shared/ReactDebugTool.js
+1
@@ -263,6 +263,7 @@ var ReactDebugTool = {
263 },
264 onSetChildren(debugID, childDebugIDs) {
265 checkDebugID(debugID);
266 + childDebugIDs.forEach(checkDebugID);
267 emitEvent('onSetChildren', debugID, childDebugIDs);
268 },
269 onSetOwner(debugID, ownerDebugID) {
src/renderers/shared/devtools/__tests__/ReactComponentTreeDevtool-test.js
+125 -91
@@ -1716,103 +1716,137 @@ describe('ReactComponentTreeDevtool', () => {
1716 expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
1717 });
1718
1719 - it('creates current stack addenda', () => {
1720 - function getAddendum(element) {
1721 - var addendum = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
1722 - return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
1723 - }
1719 + describe('stack addenda', () => {
1720 + it('gets created', () => {
1721 + function getAddendum(element) {
1722 + var addendum = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
1723 + return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
1724 + }
1725
1725 - var Anon = React.createClass({displayName: null, render: () => null});
1726 - var Orange = React.createClass({render: () => null});
1727 -
1728 - expect(getAddendum()).toBe(
1729 - ''
1730 - );
1731 - expect(getAddendum(<div />)).toBe(
1732 - '\n in div (at **)'
1733 - );
1734 - expect(getAddendum(<Anon />)).toBe(
1735 - '\n in Unknown (at **)'
1736 - );
1737 - expect(getAddendum(<Orange />)).toBe(
1738 - '\n in Orange (at **)'
1739 - );
1740 - expect(getAddendum(React.createElement(Orange))).toBe(
1741 - '\n in Orange'
1742 - );
1743 -
1744 - var renders = 0;
1745 - var rOwnedByQ;
1746 -
1747 - function Q() {
1748 - return (rOwnedByQ = React.createElement(R));
1749 - }
1750 - function R() {
1751 - return <div><S /></div>;
1752 - }
1753 - class S extends React.Component {
1754 - componentDidMount() {
1755 - // Check that the parent path is still fetched when only S itself is on
1756 - // the stack.
1757 - this.forceUpdate();
1726 + var Anon = React.createClass({displayName: null, render: () => null});
1727 + var Orange = React.createClass({render: () => null});
1728 +
1729 + expect(getAddendum()).toBe(
1730 + ''
1731 + );
1732 + expect(getAddendum(<div />)).toBe(
1733 + '\n in div (at **)'
1734 + );
1735 + expect(getAddendum(<Anon />)).toBe(
1736 + '\n in Unknown (at **)'
1737 + );
1738 + expect(getAddendum(<Orange />)).toBe(
1739 + '\n in Orange (at **)'
1740 + );
1741 + expect(getAddendum(React.createElement(Orange))).toBe(
1742 + '\n in Orange'
1743 + );
1744 +
1745 + var renders = 0;
1746 + var rOwnedByQ;
1747 +
1748 + function Q() {
1749 + return (rOwnedByQ = React.createElement(R));
1750 }
1759 - render() {
1760 - expect(getAddendum()).toBe(
1761 - '\n in S (at **)' +
1762 - '\n in div (at **)' +
1763 - '\n in R (created by Q)' +
1764 - '\n in Q (at **)'
1765 - );
1766 - expect(getAddendum(<span />)).toBe(
1767 - '\n in span (at **)' +
1768 - '\n in S (at **)' +
1769 - '\n in div (at **)' +
1770 - '\n in R (created by Q)' +
1771 - '\n in Q (at **)'
1772 - );
1773 - expect(getAddendum(React.createElement('span'))).toBe(
1774 - '\n in span (created by S)' +
1775 - '\n in S (at **)' +
1776 - '\n in div (at **)' +
1777 - '\n in R (created by Q)' +
1778 - '\n in Q (at **)'
1779 - );
1780 - renders++;
1781 - return null;
1751 + function R() {
1752 + return <div><S /></div>;
1753 }
1783 - }
1784 - ReactDOM.render(<Q />, document.createElement('div'));
1785 - expect(renders).toBe(2);
1754 + class S extends React.Component {
1755 + componentDidMount() {
1756 + // Check that the parent path is still fetched when only S itself is on
1757 + // the stack.
1758 + this.forceUpdate();
1759 + }
1760 + render() {
1761 + expect(getAddendum()).toBe(
1762 + '\n in S (at **)' +
1763 + '\n in div (at **)' +
1764 + '\n in R (created by Q)' +
1765 + '\n in Q (at **)'
1766 + );
1767 + expect(getAddendum(<span />)).toBe(
1768 + '\n in span (at **)' +
1769 + '\n in S (at **)' +
1770 + '\n in div (at **)' +
1771 + '\n in R (created by Q)' +
1772 + '\n in Q (at **)'
1773 + );
1774 + expect(getAddendum(React.createElement('span'))).toBe(
1775 + '\n in span (created by S)' +
1776 + '\n in S (at **)' +
1777 + '\n in div (at **)' +
1778 + '\n in R (created by Q)' +
1779 + '\n in Q (at **)'
1780 + );
1781 + renders++;
1782 + return null;
1783 + }
1784 + }
1785 + ReactDOM.render(<Q />, document.createElement('div'));
1786 + expect(renders).toBe(2);
1787
1787 - // Make sure owner is fetched for the top element too.
1788 - expect(getAddendum(rOwnedByQ)).toBe(
1789 - '\n in R (created by Q)'
1790 - );
1791 - });
1788 + // Make sure owner is fetched for the top element too.
1789 + expect(getAddendum(rOwnedByQ)).toBe(
1790 + '\n in R (created by Q)'
1791 + );
1792 + });
1793
1793 - it('creates stack addenda by ID', () => {
1794 - function getAddendum(id) {
1795 - var addendum = ReactComponentTreeDevtool.getStackAddendumByID(id);
1796 - return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
1797 - }
1794 + it('can be retrieved by ID', () => {
1795 + function getAddendum(id) {
1796 + var addendum = ReactComponentTreeDevtool.getStackAddendumByID(id);
1797 + return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
1798 + }
1799
1799 - class Q extends React.Component {
1800 - render() {
1801 - return null;
1800 + class Q extends React.Component {
1801 + render() {
1802 + return null;
1803 + }
1804 }
1803 - }
1805
1805 - var q = ReactDOM.render(<Q />, document.createElement('div'));
1806 - expect(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe(
1807 - '\n in Q (at **)'
1808 - );
1809 -
1810 - spyOn(console, 'error');
1811 - getAddendum(-17);
1812 - expect(console.error.calls.count()).toBe(1);
1813 - expect(console.error.calls.argsFor(0)[0]).toBe(
1814 - 'Warning: ReactComponentTreeDevtool: Missing React element for debugID ' +
1815 - '-17 when building stack'
1816 - );
1817 - });
1806 + var q = ReactDOM.render(<Q />, document.createElement('div'));
1807 + expect(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe(
1808 + '\n in Q (at **)'
1809 + );
1810 +
1811 + spyOn(console, 'error');
1812 + getAddendum(-17);
1813 + expect(console.error.calls.count()).toBe(1);
1814 + expect(console.error.calls.argsFor(0)[0]).toBe(
1815 + 'Warning: ReactComponentTreeDevtool: Missing React element for ' +
1816 + 'debugID -17 when building stack'
1817 + );
1818 + });
1819 +
1820 + it('is created during mounting', () => {
1821 + // https://github.com/facebook/react/issues/7187
1822 + var el = document.createElement('div');
1823 + var portalEl = document.createElement('div');
1824 + class Foo extends React.Component {
1825 + componentWillMount() {
1826 + ReactDOM.render(<div />, portalEl);
1827 + }
1828 + render() {
1829 + return <div><div /></div>;
1830 + }
1831 + }
1832 + ReactDOM.render(<Foo />, el);
1833 + });
1834 +
1835 + it('is created when calling renderToString during render', () => {
1836 + // https://github.com/facebook/react/issues/7190
1837 + var el = document.createElement('div');
1838 + class Foo extends React.Component {
1839 + render() {
1840 + return (
1841 + <div>
1842 + <div>
1843 + {ReactDOMServer.renderToString(<div />)}
1844 + </div>
1845 + </div>
1846 + );
1847 + }
1848 + }
1849 + ReactDOM.render(<Foo />, el);
1850 + });
1851 + })
1852 });
src/renderers/shared/stack/reconciler/ReactChildReconciler.js
+2 -2
@@ -34,7 +34,7 @@ function instantiateChild(childInstances, child, name, selfDebugID) {
34 );
35 }
36 if (child != null && keyUnique) {
37 - childInstances[name] = instantiateReactComponent(child);
37 + childInstances[name] = instantiateReactComponent(child, true);
38 }
39 }
40
@@ -128,7 +128,7 @@ var ReactChildReconciler = {
128 ReactReconciler.unmountComponent(prevChild, false);
129 }
130 // The child must be instantiated before it's mounted.
131 - var nextChildInstance = instantiateReactComponent(nextElement);
131 + var nextChildInstance = instantiateReactComponent(nextElement, true);
132 nextChildren[name] = nextChildInstance;
133 // Creating mount image now ensures refs are resolved in right order
134 // (see https://github.com/facebook/react/pull/7101 for explanation).
src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
+8 -4
@@ -473,9 +473,11 @@ var ReactCompositeComponentMixin = {
473 renderedElement = this._renderValidatedComponent();
474 }
475
476 - this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
476 + var nodeType = ReactNodeTypes.getType(renderedElement);
477 + this._renderedNodeType = nodeType;
478 var child = this._instantiateReactComponent(
478 - renderedElement
479 + renderedElement,
480 + nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
481 );
482 this._renderedComponent = child;
483 if (__DEV__) {
@@ -990,9 +992,11 @@ var ReactCompositeComponentMixin = {
992 var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
993 ReactReconciler.unmountComponent(prevComponentInstance, false);
994
993 - this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement);
995 + var nodeType = ReactNodeTypes.getType(nextRenderedElement);
996 + this._renderedNodeType = nodeType;
997 var child = this._instantiateReactComponent(
995 - nextRenderedElement
998 + nextRenderedElement,
999 + nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
1000 );
1001 this._renderedComponent = child;
1002 if (__DEV__) {
src/renderers/shared/stack/reconciler/ReactMultiChild.js
+9 -4
@@ -162,10 +162,15 @@ if (__DEV__) {
162 }
163 };
164 setChildrenForInstrumentation = function(children) {
165 - ReactInstrumentation.debugTool.onSetChildren(
166 - getDebugID(this),
167 - children ? Object.keys(children).map(key => children[key]._debugID) : []
168 - );
165 + var debugID = getDebugID(this);
166 + // TODO: React Native empty components are also multichild.
167 + // This means they still get into this method but don't have _debugID.
168 + if (debugID !== 0) {
169 + ReactInstrumentation.debugTool.onSetChildren(
170 + debugID,
171 + children ? Object.keys(children).map(key => children[key]._debugID) : []
172 + );
173 + }
174 };
175 }
176
src/renderers/shared/stack/reconciler/instantiateReactComponent.js
+8 -7
@@ -78,14 +78,14 @@ var nextDebugID = 1;
78 * Given a ReactNode, create an instance that will actually be mounted.
79 *
80 * @param {ReactNode} node
81 + * @param {boolean} shouldHaveDebugID
82 * @return {object} A new instance of the element's constructor.
83 * @protected
84 */
84 -function instantiateReactComponent(node) {
85 +function instantiateReactComponent(node, shouldHaveDebugID) {
86 var instance;
87
87 - var isEmpty = node === null || node === false;
88 - if (isEmpty) {
88 + if (node === null || node === false) {
89 instance = ReactEmptyComponent.create(instantiateReactComponent);
90 } else if (typeof node === 'object') {
91 var element = node;
@@ -141,16 +141,17 @@ function instantiateReactComponent(node) {
141 instance._mountImage = null;
142
143 if (__DEV__) {
144 - var debugID = isEmpty ? 0 : nextDebugID++;
145 - instance._debugID = debugID;
146 -
147 - if (debugID !== 0) {
144 + if (shouldHaveDebugID) {
145 + var debugID = nextDebugID++;
146 + instance._debugID = debugID;
147 var displayName = getDisplayName(instance);
148 ReactInstrumentation.debugTool.onSetDisplayName(debugID, displayName);
149 var owner = node && node._owner;
150 if (owner) {
151 ReactInstrumentation.debugTool.onSetOwner(debugID, owner._debugID);
152 }
153 + } else {
154 + instance._debugID = 0;
155 }
156 }
157
src/renderers/testing/ReactTestMount.js
+2 -8
@@ -120,16 +120,10 @@ var ReactHostMount = {
120 // }
121 // }
122
123 - var instance = instantiateReactComponent(nextWrappedElement);
123 + var instance = instantiateReactComponent(nextWrappedElement, false);
124
125 if (__DEV__) {
126 - // Mute future events from the top level wrapper.
127 - // It is an implementation detail that devtools should not know about.
128 - instance._debugID = 0;
129 -
130 - if (__DEV__) {
131 - ReactInstrumentation.debugTool.onBeginFlush();
132 - }
126 + ReactInstrumentation.debugTool.onBeginFlush();
127 }
128
129 // The initial render is synchronous but any updates that happen during