diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js
index fd61c2f000..622bad9847 100644
--- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js
+++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js
@@ -32,10 +32,6 @@ function updateTree(id, update) {
isMounted: false,
updateCount: 0,
};
- // TODO: We need to do this awkward dance because TopLevelWrapper "never
- // gets mounted" but its display name gets set in instantiateReactComponent
- // before its debug ID is set to 0.
- unmountedIDs[id] = true;
}
update(tree[id]);
}
@@ -148,7 +144,6 @@ var ReactComponentTreeDevtool = {
onMountComponent(id) {
updateTree(id, item => item.isMounted = true);
- delete unmountedIDs[id];
},
onMountRootComponent(id) {
diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js
index d7fb1b561f..baab5099fc 100644
--- a/src/renderers/dom/client/ReactMount.js
+++ b/src/renderers/dom/client/ReactMount.js
@@ -342,13 +342,7 @@ var ReactMount = {
);
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
- var componentInstance = instantiateReactComponent(nextElement);
-
- if (__DEV__) {
- // Mute future events from the top level wrapper.
- // It is an implementation detail that devtools should not know about.
- componentInstance._debugID = 0;
- }
+ var componentInstance = instantiateReactComponent(nextElement, false);
// The initial render is synchronous but any updates that happen during
// rendering, in componentWillMount or componentDidMount, will be batched
diff --git a/src/renderers/dom/server/ReactServerRendering.js b/src/renderers/dom/server/ReactServerRendering.js
index ee57e07438..a07bc05b48 100644
--- a/src/renderers/dom/server/ReactServerRendering.js
+++ b/src/renderers/dom/server/ReactServerRendering.js
@@ -40,7 +40,7 @@ function renderToStringImpl(element, makeStaticMarkup) {
if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}
- var componentInstance = instantiateReactComponent(element);
+ var componentInstance = instantiateReactComponent(element, true);
var markup = ReactReconciler.mountComponent(
componentInstance,
transaction,
diff --git a/src/renderers/native/ReactNativeMount.js b/src/renderers/native/ReactNativeMount.js
index d9fe2f2862..7e2e0fb862 100644
--- a/src/renderers/native/ReactNativeMount.js
+++ b/src/renderers/native/ReactNativeMount.js
@@ -135,17 +135,11 @@ var ReactNativeMount = {
ReactNativeTagHandles.assertRootTag(containerTag);
- var instance = instantiateReactComponent(nextWrappedElement);
+ var instance = instantiateReactComponent(nextWrappedElement, false);
ReactNativeMount._instancesByContainerID[containerTag] = instance;
if (__DEV__) {
- // Mute future events from the top level wrapper.
- // It is an implementation detail that devtools should not know about.
- instance._debugID = 0;
-
- if (__DEV__) {
- ReactInstrumentation.debugTool.onBeginFlush();
- }
+ ReactInstrumentation.debugTool.onBeginFlush();
}
// The initial render is synchronous but any updates that happen during
diff --git a/src/renderers/shared/ReactDebugTool.js b/src/renderers/shared/ReactDebugTool.js
index 62113ba8de..97907beadc 100644
--- a/src/renderers/shared/ReactDebugTool.js
+++ b/src/renderers/shared/ReactDebugTool.js
@@ -263,6 +263,7 @@ var ReactDebugTool = {
},
onSetChildren(debugID, childDebugIDs) {
checkDebugID(debugID);
+ childDebugIDs.forEach(checkDebugID);
emitEvent('onSetChildren', debugID, childDebugIDs);
},
onSetOwner(debugID, ownerDebugID) {
diff --git a/src/renderers/shared/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/renderers/shared/devtools/__tests__/ReactComponentTreeDevtool-test.js
index 534e9bd913..f62dd2a2aa 100644
--- a/src/renderers/shared/devtools/__tests__/ReactComponentTreeDevtool-test.js
+++ b/src/renderers/shared/devtools/__tests__/ReactComponentTreeDevtool-test.js
@@ -1716,103 +1716,137 @@ describe('ReactComponentTreeDevtool', () => {
expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
});
- it('creates current stack addenda', () => {
- function getAddendum(element) {
- var addendum = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
- return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
- }
-
- var Anon = React.createClass({displayName: null, render: () => null});
- var Orange = React.createClass({render: () => null});
-
- expect(getAddendum()).toBe(
- ''
- );
- expect(getAddendum(
)).toBe(
- '\n in div (at **)'
- );
- expect(getAddendum()).toBe(
- '\n in Unknown (at **)'
- );
- expect(getAddendum()).toBe(
- '\n in Orange (at **)'
- );
- expect(getAddendum(React.createElement(Orange))).toBe(
- '\n in Orange'
- );
-
- var renders = 0;
- var rOwnedByQ;
-
- function Q() {
- return (rOwnedByQ = React.createElement(R));
- }
- function R() {
- return
;
- }
- class S extends React.Component {
- componentDidMount() {
- // Check that the parent path is still fetched when only S itself is on
- // the stack.
- this.forceUpdate();
+ describe('stack addenda', () => {
+ it('gets created', () => {
+ function getAddendum(element) {
+ var addendum = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
+ return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
}
- render() {
- expect(getAddendum()).toBe(
- '\n in S (at **)' +
- '\n in div (at **)' +
- '\n in R (created by Q)' +
- '\n in Q (at **)'
- );
- expect(getAddendum()).toBe(
- '\n in span (at **)' +
- '\n in S (at **)' +
- '\n in div (at **)' +
- '\n in R (created by Q)' +
- '\n in Q (at **)'
- );
- expect(getAddendum(React.createElement('span'))).toBe(
- '\n in span (created by S)' +
- '\n in S (at **)' +
- '\n in div (at **)' +
- '\n in R (created by Q)' +
- '\n in Q (at **)'
- );
- renders++;
- return null;
+
+ var Anon = React.createClass({displayName: null, render: () => null});
+ var Orange = React.createClass({render: () => null});
+
+ expect(getAddendum()).toBe(
+ ''
+ );
+ expect(getAddendum()).toBe(
+ '\n in div (at **)'
+ );
+ expect(getAddendum()).toBe(
+ '\n in Unknown (at **)'
+ );
+ expect(getAddendum()).toBe(
+ '\n in Orange (at **)'
+ );
+ expect(getAddendum(React.createElement(Orange))).toBe(
+ '\n in Orange'
+ );
+
+ var renders = 0;
+ var rOwnedByQ;
+
+ function Q() {
+ return (rOwnedByQ = React.createElement(R));
}
- }
- ReactDOM.render(, document.createElement('div'));
- expect(renders).toBe(2);
-
- // Make sure owner is fetched for the top element too.
- expect(getAddendum(rOwnedByQ)).toBe(
- '\n in R (created by Q)'
- );
- });
-
- it('creates stack addenda by ID', () => {
- function getAddendum(id) {
- var addendum = ReactComponentTreeDevtool.getStackAddendumByID(id);
- return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
- }
-
- class Q extends React.Component {
- render() {
- return null;
+ function R() {
+ return
;
}
- }
+ class S extends React.Component {
+ componentDidMount() {
+ // Check that the parent path is still fetched when only S itself is on
+ // the stack.
+ this.forceUpdate();
+ }
+ render() {
+ expect(getAddendum()).toBe(
+ '\n in S (at **)' +
+ '\n in div (at **)' +
+ '\n in R (created by Q)' +
+ '\n in Q (at **)'
+ );
+ expect(getAddendum()).toBe(
+ '\n in span (at **)' +
+ '\n in S (at **)' +
+ '\n in div (at **)' +
+ '\n in R (created by Q)' +
+ '\n in Q (at **)'
+ );
+ expect(getAddendum(React.createElement('span'))).toBe(
+ '\n in span (created by S)' +
+ '\n in S (at **)' +
+ '\n in div (at **)' +
+ '\n in R (created by Q)' +
+ '\n in Q (at **)'
+ );
+ renders++;
+ return null;
+ }
+ }
+ ReactDOM.render(, document.createElement('div'));
+ expect(renders).toBe(2);
- var q = ReactDOM.render(, document.createElement('div'));
- expect(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe(
- '\n in Q (at **)'
- );
+ // Make sure owner is fetched for the top element too.
+ expect(getAddendum(rOwnedByQ)).toBe(
+ '\n in R (created by Q)'
+ );
+ });
- spyOn(console, 'error');
- getAddendum(-17);
- expect(console.error.calls.count()).toBe(1);
- expect(console.error.calls.argsFor(0)[0]).toBe(
- 'Warning: ReactComponentTreeDevtool: Missing React element for debugID ' +
- '-17 when building stack'
- );
- });
+ it('can be retrieved by ID', () => {
+ function getAddendum(id) {
+ var addendum = ReactComponentTreeDevtool.getStackAddendumByID(id);
+ return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
+ }
+
+ class Q extends React.Component {
+ render() {
+ return null;
+ }
+ }
+
+ var q = ReactDOM.render(, document.createElement('div'));
+ expect(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe(
+ '\n in Q (at **)'
+ );
+
+ spyOn(console, 'error');
+ getAddendum(-17);
+ expect(console.error.calls.count()).toBe(1);
+ expect(console.error.calls.argsFor(0)[0]).toBe(
+ 'Warning: ReactComponentTreeDevtool: Missing React element for ' +
+ 'debugID -17 when building stack'
+ );
+ });
+
+ it('is created during mounting', () => {
+ // https://github.com/facebook/react/issues/7187
+ var el = document.createElement('div');
+ var portalEl = document.createElement('div');
+ class Foo extends React.Component {
+ componentWillMount() {
+ ReactDOM.render(, portalEl);
+ }
+ render() {
+ return ;
+ }
+ }
+ ReactDOM.render(, el);
+ });
+
+ it('is created when calling renderToString during render', () => {
+ // https://github.com/facebook/react/issues/7190
+ var el = document.createElement('div');
+ class Foo extends React.Component {
+ render() {
+ return (
+
+
+ {ReactDOMServer.renderToString(
)}
+
+
+ );
+ }
+ }
+ ReactDOM.render(, el);
+ });
+ })
});
diff --git a/src/renderers/shared/stack/reconciler/ReactChildReconciler.js b/src/renderers/shared/stack/reconciler/ReactChildReconciler.js
index 2e7479bd0d..40f589c435 100644
--- a/src/renderers/shared/stack/reconciler/ReactChildReconciler.js
+++ b/src/renderers/shared/stack/reconciler/ReactChildReconciler.js
@@ -34,7 +34,7 @@ function instantiateChild(childInstances, child, name, selfDebugID) {
);
}
if (child != null && keyUnique) {
- childInstances[name] = instantiateReactComponent(child);
+ childInstances[name] = instantiateReactComponent(child, true);
}
}
@@ -128,7 +128,7 @@ var ReactChildReconciler = {
ReactReconciler.unmountComponent(prevChild, false);
}
// The child must be instantiated before it's mounted.
- var nextChildInstance = instantiateReactComponent(nextElement);
+ var nextChildInstance = instantiateReactComponent(nextElement, true);
nextChildren[name] = nextChildInstance;
// Creating mount image now ensures refs are resolved in right order
// (see https://github.com/facebook/react/pull/7101 for explanation).
diff --git a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
index 142598ecd4..22bd62ff70 100644
--- a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
+++ b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
@@ -473,9 +473,11 @@ var ReactCompositeComponentMixin = {
renderedElement = this._renderValidatedComponent();
}
- this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
+ var nodeType = ReactNodeTypes.getType(renderedElement);
+ this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(
- renderedElement
+ renderedElement,
+ nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
);
this._renderedComponent = child;
if (__DEV__) {
@@ -990,9 +992,11 @@ var ReactCompositeComponentMixin = {
var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
ReactReconciler.unmountComponent(prevComponentInstance, false);
- this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement);
+ var nodeType = ReactNodeTypes.getType(nextRenderedElement);
+ this._renderedNodeType = nodeType;
var child = this._instantiateReactComponent(
- nextRenderedElement
+ nextRenderedElement,
+ nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
);
this._renderedComponent = child;
if (__DEV__) {
diff --git a/src/renderers/shared/stack/reconciler/ReactMultiChild.js b/src/renderers/shared/stack/reconciler/ReactMultiChild.js
index 0e59d32d94..474ec774f0 100644
--- a/src/renderers/shared/stack/reconciler/ReactMultiChild.js
+++ b/src/renderers/shared/stack/reconciler/ReactMultiChild.js
@@ -162,10 +162,15 @@ if (__DEV__) {
}
};
setChildrenForInstrumentation = function(children) {
- ReactInstrumentation.debugTool.onSetChildren(
- getDebugID(this),
- children ? Object.keys(children).map(key => children[key]._debugID) : []
- );
+ var debugID = getDebugID(this);
+ // TODO: React Native empty components are also multichild.
+ // This means they still get into this method but don't have _debugID.
+ if (debugID !== 0) {
+ ReactInstrumentation.debugTool.onSetChildren(
+ debugID,
+ children ? Object.keys(children).map(key => children[key]._debugID) : []
+ );
+ }
};
}
diff --git a/src/renderers/shared/stack/reconciler/instantiateReactComponent.js b/src/renderers/shared/stack/reconciler/instantiateReactComponent.js
index bbedb68cfa..658983fa0d 100644
--- a/src/renderers/shared/stack/reconciler/instantiateReactComponent.js
+++ b/src/renderers/shared/stack/reconciler/instantiateReactComponent.js
@@ -78,14 +78,14 @@ var nextDebugID = 1;
* Given a ReactNode, create an instance that will actually be mounted.
*
* @param {ReactNode} node
+ * @param {boolean} shouldHaveDebugID
* @return {object} A new instance of the element's constructor.
* @protected
*/
-function instantiateReactComponent(node) {
+function instantiateReactComponent(node, shouldHaveDebugID) {
var instance;
- var isEmpty = node === null || node === false;
- if (isEmpty) {
+ if (node === null || node === false) {
instance = ReactEmptyComponent.create(instantiateReactComponent);
} else if (typeof node === 'object') {
var element = node;
@@ -141,16 +141,17 @@ function instantiateReactComponent(node) {
instance._mountImage = null;
if (__DEV__) {
- var debugID = isEmpty ? 0 : nextDebugID++;
- instance._debugID = debugID;
-
- if (debugID !== 0) {
+ if (shouldHaveDebugID) {
+ var debugID = nextDebugID++;
+ instance._debugID = debugID;
var displayName = getDisplayName(instance);
ReactInstrumentation.debugTool.onSetDisplayName(debugID, displayName);
var owner = node && node._owner;
if (owner) {
ReactInstrumentation.debugTool.onSetOwner(debugID, owner._debugID);
}
+ } else {
+ instance._debugID = 0;
}
}
diff --git a/src/renderers/testing/ReactTestMount.js b/src/renderers/testing/ReactTestMount.js
index 45e6cfd290..cfa9325b92 100644
--- a/src/renderers/testing/ReactTestMount.js
+++ b/src/renderers/testing/ReactTestMount.js
@@ -120,16 +120,10 @@ var ReactHostMount = {
// }
// }
- var instance = instantiateReactComponent(nextWrappedElement);
+ var instance = instantiateReactComponent(nextWrappedElement, false);
if (__DEV__) {
- // Mute future events from the top level wrapper.
- // It is an implementation detail that devtools should not know about.
- instance._debugID = 0;
-
- if (__DEV__) {
- ReactInstrumentation.debugTool.onBeginFlush();
- }
+ ReactInstrumentation.debugTool.onBeginFlush();
}
// The initial render is synchronous but any updates that happen during