diff --git a/src/renderers/dom/__tests__/ReactDOMProduction-test.js b/src/renderers/dom/__tests__/ReactDOMProduction-test.js
index a50e7eedc01af..5b1d4ebf94835 100644
--- a/src/renderers/dom/__tests__/ReactDOMProduction-test.js
+++ b/src/renderers/dom/__tests__/ReactDOMProduction-test.js
@@ -86,6 +86,94 @@ describe('ReactDOMProduction', function() {
expect(container.childNodes.length).toBe(0);
});
+ it('should call lifecycle methods', function() {
+ var log = [];
+ class Component extends React.Component {
+ state = {y: 1};
+ shouldComponentUpdate(nextProps, nextState) {
+ log.push(['shouldComponentUpdate', nextProps, nextState]);
+ return nextProps.x !== this.props.x || nextState.y !== this.state.y;
+ }
+ componentWillMount() {
+ log.push(['componentWillMount']);
+ }
+ componentDidMount() {
+ log.push(['componentDidMount']);
+ }
+ componentWillReceiveProps(nextProps) {
+ log.push(['componentWillReceiveProps', nextProps]);
+ }
+ componentWillUpdate(nextProps, nextState) {
+ log.push(['componentWillUpdate', nextProps, nextState]);
+ }
+ componentDidUpdate(prevProps, prevState) {
+ log.push(['componentDidUpdate', prevProps, prevState]);
+ }
+ componentWillUnmount() {
+ log.push(['componentWillUnmount']);
+ }
+ render() {
+ log.push(['render']);
+ return null;
+ }
+ }
+
+ var container = document.createElement('div');
+ var inst = ReactDOM.render(
+ ,
+ container
+ );
+ expect(log).toEqual([
+ ['componentWillMount'],
+ ['render'],
+ ['componentDidMount'],
+ ]);
+ log = [];
+
+ inst.setState({y: 2});
+ expect(log).toEqual([
+ ['shouldComponentUpdate', {x: 1}, {y: 2}],
+ ['componentWillUpdate', {x: 1}, {y: 2}],
+ ['render'],
+ ['componentDidUpdate', {x: 1}, {y: 1}],
+ ]);
+ log = [];
+
+ inst.setState({y: 2});
+ expect(log).toEqual([
+ ['shouldComponentUpdate', {x: 1}, {y: 2}],
+ ]);
+ log = [];
+
+ ReactDOM.render(
+ ,
+ container
+ );
+ expect(log).toEqual([
+ ['componentWillReceiveProps', {x: 2}],
+ ['shouldComponentUpdate', {x: 2}, {y: 2}],
+ ['componentWillUpdate', {x: 2}, {y: 2}],
+ ['render'],
+ ['componentDidUpdate', {x: 1}, {y: 2}],
+ ]);
+ log = [];
+
+ ReactDOM.render(
+ ,
+ container
+ );
+ expect(log).toEqual([
+ ['componentWillReceiveProps', {x: 2}],
+ ['shouldComponentUpdate', {x: 2}, {y: 2}],
+ ]);
+ log = [];
+
+ ReactDOM.unmountComponentAtNode(container);
+ expect(log).toEqual([
+ ['componentWillUnmount'],
+ ]);
+ });
+
it('should throw with an error code in production', function() {
expect(function() {
class Component extends React.Component {
diff --git a/src/renderers/shared/ReactDebugTool.js b/src/renderers/shared/ReactDebugTool.js
index e976445c3d635..a59053d3a35d7 100644
--- a/src/renderers/shared/ReactDebugTool.js
+++ b/src/renderers/shared/ReactDebugTool.js
@@ -251,12 +251,6 @@ var ReactDebugTool = {
endLifeCycleTimer(debugID, timerType);
emitEvent('onEndLifeCycleTimer', debugID, timerType);
},
- onError(debugID) {
- if (currentTimerDebugID != null) {
- endLifeCycleTimer(currentTimerDebugID, currentTimerType);
- }
- emitEvent('onError', debugID);
- },
onBeginProcessingChildContext() {
emitEvent('onBeginProcessingChildContext');
},
diff --git a/src/renderers/shared/__tests__/ReactPerf-test.js b/src/renderers/shared/__tests__/ReactPerf-test.js
index 99e5c9494312e..70e5cd5f53fa3 100644
--- a/src/renderers/shared/__tests__/ReactPerf-test.js
+++ b/src/renderers/shared/__tests__/ReactPerf-test.js
@@ -516,4 +516,208 @@ describe('ReactPerf', function() {
renderCount: 2,
}]);
});
+
+ it('should not print errant warnings if render() throws', () => {
+ var container = document.createElement('div');
+ var thrownErr = new Error('Muhaha!');
+
+ class Evil extends React.Component {
+ render() {
+ throw thrownErr;
+ }
+ }
+
+ ReactPerf.start();
+ try {
+ ReactDOM.render(
+
+
+
+
,
+ container
+ );
+ } catch (err) {
+ if (err !== thrownErr) {
+ throw err;
+ }
+ }
+ ReactPerf.stop();
+ });
+
+ it('should not print errant warnings if componentWillMount() throws', () => {
+ var container = document.createElement('div');
+ var thrownErr = new Error('Muhaha!');
+
+ class Evil extends React.Component {
+ componentWillMount() {
+ throw thrownErr;
+ }
+ render() {
+ return ;
+ }
+ }
+
+ ReactPerf.start();
+ try {
+ ReactDOM.render(
+
+
+
+
,
+ container
+ );
+ } catch (err) {
+ if (err !== thrownErr) {
+ throw err;
+ }
+ }
+ ReactPerf.stop();
+ });
+
+ it('should not print errant warnings if componentDidMount() throws', () => {
+ var container = document.createElement('div');
+ var thrownErr = new Error('Muhaha!');
+
+ class Evil extends React.Component {
+ componentDidMount() {
+ throw thrownErr;
+ }
+ render() {
+ return ;
+ }
+ }
+
+ ReactPerf.start();
+ try {
+ ReactDOM.render(
+
+
+
+
,
+ container
+ );
+ } catch (err) {
+ if (err !== thrownErr) {
+ throw err;
+ }
+ }
+ ReactPerf.stop();
+ });
+
+ it('should not print errant warnings if portal throws in render()', () => {
+ var container = document.createElement('div');
+ var thrownErr = new Error('Muhaha!');
+
+ class Evil extends React.Component {
+ render() {
+ throw thrownErr;
+ }
+ }
+ class EvilPortal extends React.Component {
+ componentWillMount() {
+ var portalContainer = document.createElement('div');
+ ReactDOM.render(, portalContainer);
+ }
+ render() {
+ return ;
+ }
+ }
+
+ ReactPerf.start();
+ try {
+ ReactDOM.render(
+
+
+
+
,
+ container
+ );
+ } catch (err) {
+ if (err !== thrownErr) {
+ throw err;
+ }
+ }
+ ReactDOM.unmountComponentAtNode(container);
+ ReactPerf.stop();
+ });
+
+ it('should not print errant warnings if portal throws in componentWillMount()', () => {
+ var container = document.createElement('div');
+ var thrownErr = new Error('Muhaha!');
+
+ class Evil extends React.Component {
+ componentWillMount() {
+ throw thrownErr;
+ }
+ render() {
+ return ;
+ }
+ }
+ class EvilPortal extends React.Component {
+ componentWillMount() {
+ var portalContainer = document.createElement('div');
+ ReactDOM.render(, portalContainer);
+ }
+ render() {
+ return ;
+ }
+ }
+
+ ReactPerf.start();
+ try {
+ ReactDOM.render(
+
+
+
+
,
+ container
+ );
+ } catch (err) {
+ if (err !== thrownErr) {
+ throw err;
+ }
+ }
+ ReactDOM.unmountComponentAtNode(container);
+ ReactPerf.stop();
+ });
+
+ it('should not print errant warnings if portal throws in componentDidMount()', () => {
+ var container = document.createElement('div');
+ var thrownErr = new Error('Muhaha!');
+
+ class Evil extends React.Component {
+ componentDidMount() {
+ throw thrownErr;
+ }
+ render() {
+ return ;
+ }
+ }
+ class EvilPortal extends React.Component {
+ componentWillMount() {
+ var portalContainer = document.createElement('div');
+ ReactDOM.render(, portalContainer);
+ }
+ render() {
+ return ;
+ }
+ }
+
+ ReactPerf.start();
+ try {
+ ReactDOM.render(
+
+
+
+
,
+ container
+ );
+ } catch (err) {
+ if (err !== thrownErr) {
+ throw err;
+ }
+ }
+ ReactDOM.unmountComponentAtNode(container);
+ ReactPerf.stop();
+ });
});
diff --git a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
index bce59875a2e08..9536502084050 100644
--- a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
+++ b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
@@ -59,40 +59,6 @@ function warnIfInvalidElement(Component, element) {
}
}
-function invokeComponentDidMountWithTimer() {
- var publicInstance = this._instance;
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'componentDidMount'
- );
- }
- publicInstance.componentDidMount();
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
- this._debugID,
- 'componentDidMount'
- );
- }
-}
-
-function invokeComponentDidUpdateWithTimer(prevProps, prevState, prevContext) {
- var publicInstance = this._instance;
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'componentDidUpdate'
- );
- }
- publicInstance.componentDidUpdate(prevProps, prevState, prevContext);
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
- this._debugID,
- 'componentDidUpdate'
- );
- }
-}
-
function shouldConstruct(Component) {
return !!(Component.prototype && Component.prototype.isReactComponent);
}
@@ -101,6 +67,23 @@ function isPureComponent(Component) {
return !!(Component.prototype && Component.prototype.isPureReactComponent);
}
+// Separated into a function to contain deoptimizations caused by try/finally.
+function measureLifeCyclePerf(fn, debugID, timerType) {
+ if (debugID === 0) {
+ // Top-level wrappers (see ReactMount) and empty components (see
+ // ReactDOMEmptyComponent) are invisible to hooks and devtools.
+ // Both are implementation details that should go away in the future.
+ return fn();
+ }
+
+ ReactInstrumentation.debugTool.onBeginLifeCycleTimer(debugID, timerType);
+ try {
+ return fn();
+ } finally {
+ ReactInstrumentation.debugTool.onEndLifeCycleTimer(debugID, timerType);
+ }
+}
+
/**
* ------------------ The Life-Cycle of a Composite Component ------------------
*
@@ -361,7 +344,13 @@ var ReactCompositeComponentMixin = {
if (inst.componentDidMount) {
if (__DEV__) {
- transaction.getReactMountReady().enqueue(invokeComponentDidMountWithTimer, this);
+ transaction.getReactMountReady().enqueue(() => {
+ measureLifeCyclePerf(
+ () => inst.componentDidMount(),
+ this._debugID,
+ 'componentDidMount'
+ );
+ });
} else {
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
}
@@ -405,47 +394,30 @@ var ReactCompositeComponentMixin = {
updateQueue
) {
var Component = this._currentElement.type;
- var instanceOrElement;
+
if (doConstruct) {
if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'ctor'
- );
- }
- }
- instanceOrElement = new Component(publicProps, publicContext, updateQueue);
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
- this._debugID,
- 'ctor'
- );
- }
+ return measureLifeCyclePerf(
+ () => new Component(publicProps, publicContext, updateQueue),
+ this._debugID,
+ 'ctor'
+ );
+ } else {
+ return new Component(publicProps, publicContext, updateQueue);
}
+ }
+
+ // This can still be an instance in case of factory components
+ // but we'll count this as time spent rendering as the more common case.
+ if (__DEV__) {
+ return measureLifeCyclePerf(
+ () => Component(publicProps, publicContext, updateQueue),
+ this._debugID,
+ 'render'
+ );
} else {
- // This can still be an instance in case of factory components
- // but we'll count this as time spent rendering as the more common case.
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'render'
- );
- }
- }
- instanceOrElement = Component(publicProps, publicContext, updateQueue);
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
- this._debugID,
- 'render'
- );
- }
- }
+ return Component(publicProps, publicContext, updateQueue);
}
- return instanceOrElement;
},
performInitialMountWithErrorHandling: function(
@@ -460,11 +432,6 @@ var ReactCompositeComponentMixin = {
try {
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
} catch (e) {
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onError();
- }
- }
// Roll back to checkpoint, handle error (which may add items to the transaction), and take a new checkpoint
transaction.rollback(checkpoint);
this._instance.unstable_handleError(e);
@@ -485,23 +452,21 @@ var ReactCompositeComponentMixin = {
performInitialMount: function(renderedElement, hostParent, hostContainerInfo, transaction, context) {
var inst = this._instance;
+
+ var debugID = 0;
+ if (__DEV__) {
+ debugID = this._debugID;
+ }
+
if (inst.componentWillMount) {
if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'componentWillMount'
- );
- }
- }
- inst.componentWillMount();
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
- this._debugID,
- 'componentWillMount'
- );
- }
+ measureLifeCyclePerf(
+ () => inst.componentWillMount(),
+ debugID,
+ 'componentWillMount'
+ );
+ } else {
+ inst.componentWillMount();
}
// When mounting, calls to `setState` by `componentWillMount` will set
// `this._pendingStateQueue` without triggering a re-render.
@@ -523,25 +488,19 @@ var ReactCompositeComponentMixin = {
);
this._renderedComponent = child;
- var selfDebugID = 0;
- if (__DEV__) {
- selfDebugID = this._debugID;
- }
var markup = ReactReconciler.mountComponent(
child,
transaction,
hostParent,
hostContainerInfo,
this._processChildContext(context),
- selfDebugID
+ debugID
);
if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onSetChildren(
- this._debugID,
- child._debugID !== 0 ? [child._debugID] : []
- );
+ if (debugID !== 0) {
+ var childDebugIDs = child._debugID !== 0 ? [child._debugID] : [];
+ ReactInstrumentation.debugTool.onSetChildren(debugID, childDebugIDs);
}
}
@@ -562,30 +521,24 @@ var ReactCompositeComponentMixin = {
if (!this._renderedComponent) {
return;
}
+
var inst = this._instance;
if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) {
inst._calledComponentWillUnmount = true;
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'componentWillUnmount'
- );
- }
- }
+
if (safely) {
var name = this.getName() + '.componentWillUnmount()';
ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst));
} else {
- inst.componentWillUnmount();
- }
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
+ if (__DEV__) {
+ measureLifeCyclePerf(
+ () => inst.componentWillUnmount(),
this._debugID,
'componentWillUnmount'
);
+ } else {
+ inst.componentWillUnmount();
}
}
}
@@ -676,13 +629,21 @@ var ReactCompositeComponentMixin = {
_processChildContext: function(currentContext) {
var Component = this._currentElement.type;
var inst = this._instance;
- if (__DEV__) {
- ReactInstrumentation.debugTool.onBeginProcessingChildContext();
- }
- var childContext = inst.getChildContext && inst.getChildContext();
- if (__DEV__) {
- ReactInstrumentation.debugTool.onEndProcessingChildContext();
+ var childContext;
+
+ if (inst.getChildContext) {
+ if (__DEV__) {
+ ReactInstrumentation.debugTool.onBeginProcessingChildContext();
+ try {
+ childContext = inst.getChildContext();
+ } finally {
+ ReactInstrumentation.debugTool.onEndProcessingChildContext();
+ }
+ } else {
+ childContext = inst.getChildContext();
+ }
}
+
if (childContext) {
invariant(
typeof Component.childContextTypes === 'object',
@@ -826,21 +787,13 @@ var ReactCompositeComponentMixin = {
// immediately reconciled instead of waiting for the next batch.
if (willReceive && inst.componentWillReceiveProps) {
if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'componentWillReceiveProps'
- );
- }
- }
- inst.componentWillReceiveProps(nextProps, nextContext);
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
- this._debugID,
- 'componentWillReceiveProps'
- );
- }
+ measureLifeCyclePerf(
+ () => inst.componentWillReceiveProps(nextProps, nextContext),
+ this._debugID,
+ 'componentWillReceiveProps',
+ );
+ } else {
+ inst.componentWillReceiveProps(nextProps, nextContext);
}
}
@@ -850,21 +803,13 @@ var ReactCompositeComponentMixin = {
if (!this._pendingForceUpdate) {
if (inst.shouldComponentUpdate) {
if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'shouldComponentUpdate'
- );
- }
- }
- shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
- this._debugID,
- 'shouldComponentUpdate'
- );
- }
+ shouldUpdate = measureLifeCyclePerf(
+ () => inst.shouldComponentUpdate(nextProps, nextState, nextContext),
+ this._debugID,
+ 'shouldComponentUpdate'
+ );
+ } else {
+ shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
}
} else {
if (this._compositeType === CompositeTypes.PureClass) {
@@ -970,21 +915,13 @@ var ReactCompositeComponentMixin = {
if (inst.componentWillUpdate) {
if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'componentWillUpdate'
- );
- }
- }
- inst.componentWillUpdate(nextProps, nextState, nextContext);
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
- this._debugID,
- 'componentWillUpdate'
- );
- }
+ measureLifeCyclePerf(
+ () => inst.componentWillUpdate(nextProps, nextState, nextContext),
+ this._debugID,
+ 'componentWillUpdate'
+ );
+ } else {
+ inst.componentWillUpdate(nextProps, nextState, nextContext);
}
}
@@ -998,10 +935,13 @@ var ReactCompositeComponentMixin = {
if (hasComponentDidUpdate) {
if (__DEV__) {
- transaction.getReactMountReady().enqueue(
- invokeComponentDidUpdateWithTimer.bind(this, prevProps, prevState, prevContext),
- this
- );
+ transaction.getReactMountReady().enqueue(() => {
+ measureLifeCyclePerf(
+ inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
+ this._debugID,
+ 'componentDidUpdate'
+ );
+ });
} else {
transaction.getReactMountReady().enqueue(
inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
@@ -1021,6 +961,12 @@ var ReactCompositeComponentMixin = {
var prevComponentInstance = this._renderedComponent;
var prevRenderedElement = prevComponentInstance._currentElement;
var nextRenderedElement = this._renderValidatedComponent();
+
+ var debugID = 0;
+ if (__DEV__) {
+ debugID = this._debugID;
+ }
+
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
ReactReconciler.receiveComponent(
prevComponentInstance,
@@ -1040,25 +986,19 @@ var ReactCompositeComponentMixin = {
);
this._renderedComponent = child;
- var selfDebugID = 0;
- if (__DEV__) {
- selfDebugID = this._debugID;
- }
var nextMarkup = ReactReconciler.mountComponent(
child,
transaction,
this._hostParent,
this._hostContainerInfo,
this._processChildContext(context),
- selfDebugID
+ debugID
);
if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onSetChildren(
- this._debugID,
- child._debugID !== 0 ? [child._debugID] : []
- );
+ if (debugID !== 0) {
+ var childDebugIDs = child._debugID !== 0 ? [child._debugID] : [];
+ ReactInstrumentation.debugTool.onSetChildren(debugID, childDebugIDs);
}
}
@@ -1088,23 +1028,16 @@ var ReactCompositeComponentMixin = {
*/
_renderValidatedComponentWithoutOwnerOrContext: function() {
var inst = this._instance;
+ var renderedComponent;
if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
- this._debugID,
- 'render'
- );
- }
- }
- var renderedComponent = inst.render();
- if (__DEV__) {
- if (this._debugID !== 0) {
- ReactInstrumentation.debugTool.onEndLifeCycleTimer(
- this._debugID,
- 'render'
- );
- }
+ renderedComponent = measureLifeCyclePerf(
+ () => inst.render(),
+ this._debugID,
+ 'render'
+ );
+ } else {
+ renderedComponent = inst.render();
}
if (__DEV__) {