Skip to content

Commit

Permalink
Ensure lifecycle timers are stopped on errors (#7548)
Browse files Browse the repository at this point in the history
* Ensure lifecycle timers are stopped on errors

Fixes #7349

* Address review feedback and add an extra test
  • Loading branch information
gaearon committed Aug 24, 2016
1 parent 0a248ee commit a229cdb
Show file tree
Hide file tree
Showing 4 changed files with 419 additions and 200 deletions.
88 changes: 88 additions & 0 deletions src/renderers/dom/__tests__/ReactDOMProduction-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<Component x={1} />,
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(
<Component x={2} />,
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(
<Component x={2} />,
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 {
Expand Down
6 changes: 0 additions & 6 deletions src/renderers/shared/ReactDebugTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,6 @@ var ReactDebugTool = {
markEnd(debugID, timerType);
emitEvent('onEndLifeCycleTimer', debugID, timerType);
},
onError(debugID) {
if (currentTimerDebugID != null) {
endLifeCycleTimer(currentTimerDebugID, currentTimerType);
}
emitEvent('onError', debugID);
},
onBeginProcessingChildContext() {
emitEvent('onBeginProcessingChildContext');
},
Expand Down
204 changes: 204 additions & 0 deletions src/renderers/shared/__tests__/ReactPerf-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<div>
<LifeCycle />
<Evil />
</div>,
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 <div />;
}
}

ReactPerf.start();
try {
ReactDOM.render(
<div>
<LifeCycle />
<Evil />
</div>,
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 <div />;
}
}

ReactPerf.start();
try {
ReactDOM.render(
<div>
<LifeCycle />
<Evil />
</div>,
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(<Evil />, portalContainer);
}
render() {
return <div />;
}
}

ReactPerf.start();
try {
ReactDOM.render(
<div>
<LifeCycle />
<EvilPortal />
</div>,
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 <div />;
}
}
class EvilPortal extends React.Component {
componentWillMount() {
var portalContainer = document.createElement('div');
ReactDOM.render(<Evil />, portalContainer);
}
render() {
return <div />;
}
}

ReactPerf.start();
try {
ReactDOM.render(
<div>
<LifeCycle />
<EvilPortal />
</div>,
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 <div />;
}
}
class EvilPortal extends React.Component {
componentWillMount() {
var portalContainer = document.createElement('div');
ReactDOM.render(<Evil />, portalContainer);
}
render() {
return <div />;
}
}

ReactPerf.start();
try {
ReactDOM.render(
<div>
<LifeCycle />
<EvilPortal />
</div>,
container
);
} catch (err) {
if (err !== thrownErr) {
throw err;
}
}
ReactDOM.unmountComponentAtNode(container);
ReactPerf.stop();
});
});
Loading

0 comments on commit a229cdb

Please sign in to comment.