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