mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
* Handles risky callbacks on setState. Fixes #8238 * Updates try-catch to cover `callback` when context is not present. * Updates code to trapErrors instead of swallowing them. * Fixes flow errors * Incorporates review comments * Traps only the first error. Removes `callbackWasCalled` and updates fiber tests.
This commit is contained in:
committed by
Andrew Clark
parent
9d201918bf
commit
077822e9d0
@@ -806,6 +806,7 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
|
||||
* skips will/DidUpdate when bailing unless an update was already in progress
|
||||
* performs batched updates at the end of the batch
|
||||
* can nest batchedUpdates
|
||||
* can handle if setState callback throws
|
||||
|
||||
src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
|
||||
* catches render error in a boundary during mounting
|
||||
|
||||
@@ -321,7 +321,11 @@ module.exports = function<T, P, I, TI, C>(
|
||||
}
|
||||
if (finishedWork.effectTag & Callback) {
|
||||
if (finishedWork.callbackList) {
|
||||
callCallbacks(finishedWork.callbackList, instance);
|
||||
const callbackError = callCallbacks(finishedWork.callbackList, instance);
|
||||
// since we only want to keep the first error
|
||||
if (!error) {
|
||||
error = callbackError;
|
||||
}
|
||||
finishedWork.callbackList = null;
|
||||
}
|
||||
}
|
||||
@@ -332,10 +336,15 @@ module.exports = function<T, P, I, TI, C>(
|
||||
}
|
||||
case HostContainer: {
|
||||
const rootFiber = finishedWork.stateNode;
|
||||
let error = null;
|
||||
if (rootFiber.callbackList) {
|
||||
const { callbackList } = rootFiber;
|
||||
rootFiber.callbackList = null;
|
||||
callCallbacks(callbackList, rootFiber.current.child.stateNode);
|
||||
error = callCallbacks(callbackList, rootFiber.current.child.stateNode);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
trapError(rootFiber, error, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,20 +69,26 @@ exports.addCallbackToQueue = function(queue : UpdateQueue, callback: Function) :
|
||||
return queue;
|
||||
};
|
||||
|
||||
exports.callCallbacks = function(queue : UpdateQueue, context : any) {
|
||||
exports.callCallbacks = function(queue : UpdateQueue, context : any) : Error | null {
|
||||
let node : ?UpdateQueueNode = queue;
|
||||
let error = null;
|
||||
while (node) {
|
||||
const callback = node.callback;
|
||||
if (callback && !node.callbackWasCalled) {
|
||||
node.callbackWasCalled = true;
|
||||
if (typeof context !== 'undefined') {
|
||||
callback.call(context);
|
||||
} else {
|
||||
callback();
|
||||
try {
|
||||
node.callbackWasCalled = true;
|
||||
if (typeof context !== 'undefined') {
|
||||
callback.call(context);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
node = node.next;
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
exports.mergeUpdateQueue = function(queue : UpdateQueue, instance : any, prevState : any, props : any) : any {
|
||||
|
||||
@@ -1465,4 +1465,48 @@ describe('ReactIncremental', () => {
|
||||
]);
|
||||
expect(instance.state.n).toEqual(4);
|
||||
});
|
||||
|
||||
it('can handle if setState callback throws', () => {
|
||||
var ops = [];
|
||||
var instance;
|
||||
|
||||
class Foo extends React.Component {
|
||||
state = { n: 0 };
|
||||
render() {
|
||||
instance = this;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
ReactNoop.flush();
|
||||
ops = [];
|
||||
|
||||
expect(instance.state.n).toEqual(0);
|
||||
|
||||
// first good callback
|
||||
instance.setState({ n: 1 }, () => ops.push('first good callback'));
|
||||
ReactNoop.flush();
|
||||
|
||||
// callback throws
|
||||
instance.setState({ n: 2 }, () => {
|
||||
throw new Error('Bail');
|
||||
});
|
||||
expect(() => {
|
||||
ReactNoop.flush();
|
||||
}).toThrow('Bail');
|
||||
|
||||
// should set state to 2 even if callback throws up
|
||||
expect(instance.state.n).toEqual(2);
|
||||
|
||||
// another good callback
|
||||
instance.setState({ n: 3 }, () => ops.push('second good callback'));
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'first good callback',
|
||||
'second good callback',
|
||||
]);
|
||||
expect(instance.state.n).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user