diff --git a/src/renderers/shared/fiber/ReactFiberCommitWork.js b/src/renderers/shared/fiber/ReactFiberCommitWork.js index d859003383..beb773a2e7 100644 --- a/src/renderers/shared/fiber/ReactFiberCommitWork.js +++ b/src/renderers/shared/fiber/ReactFiberCommitWork.js @@ -32,12 +32,16 @@ module.exports = function(config : HostConfig) { function commitWork(current : ?Fiber, finishedWork : Fiber) : void { switch (finishedWork.tag) { case ClassComponent: { + // Clear updates from current fiber. This must go before the callbacks + // are reset, in case an update is triggered from inside a callback. Is + // this safe? Relies on the assumption that work is only committed if + // the update queue is empty. + if (finishedWork.alternate) { + finishedWork.alternate.updateQueue = null; + } if (finishedWork.callbackList) { const { callbackList } = finishedWork; finishedWork.callbackList = null; - if (finishedWork.alternate) { - finishedWork.alternate.callbackList = null; - } callCallbacks(callbackList, finishedWork.stateNode); } // TODO: Fire componentDidMount/componentDidUpdate, update refs diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 6d6f9598c4..d4687f4574 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -132,14 +132,14 @@ module.exports = function(config : HostConfig) { transferOutput(workInProgress.child, workInProgress); // Don't use the state queue to compute the memoized state. We already // merged it and assigned it to the instance. Transfer it from there. - const state = workInProgress.stateNode.state; + // Also need to transfer the props, because pendingProps will be null + // in the case of an update + const { state, props } = workInProgress.stateNode; workInProgress.memoizedState = state; + workInProgress.memoizedProps = props; // Transfer update queue to callbackList field so callbacks can be // called during commit phase. workInProgress.callbackList = workInProgress.updateQueue; - if (current) { - current.callbackList = workInProgress.callbackList; - } markForPostEffect(workInProgress); return null; case HostContainer: diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 122a64fcd5..874c24928c 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -144,9 +144,6 @@ module.exports = function(config : HostConfig) { // to the system not to redo any work here. workInProgress.pendingProps = null; workInProgress.updateQueue = null; - if (current) { - current.updateQueue = null; - } const returnFiber = workInProgress.return; diff --git a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js index 5213acfa48..c543de4549 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js @@ -654,6 +654,44 @@ describe('ReactIncremental', () => { expect(instance.state.num).toEqual(6); }); + it('can call setState inside update callback', () => { + let instance; + class Bar extends React.Component { + constructor() { + super(); + this.state = { num: 1 }; + instance = this; + } + render() { + return
{this.props.children}
; + } + } + + function Foo({ multiplier }) { + return ( +
+ +
+ ); + } + + function updater(state, props) { + return { num: state.num * props.multiplier }; + } + + function callback() { + this.setState({ called: true }); + } + + ReactNoop.render(); + ReactNoop.flush(); + instance.setState(updater); + instance.setState(updater, callback); + ReactNoop.flush(); + expect(instance.state.num).toEqual(4); + expect(instance.state.called).toEqual(true); + }); + it('can replaceState', () => { let instance; const Bar = React.createClass({