mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Don't mutate current tree before work is committed.
We should be able to abort an update without any side-effects to the current tree. This fixes a few cases where that was broken. The callback list should only ever be set on the workInProgress. There's no reason to add it to the current tree because they're not needed after they are called during the commit phase. Also found a bug where the memoizedProps were set to null in the case of an update, because the pendingProps were null. Fixed by transfering the props from the instance, like we were already doing with state. Added a test to ensure that setState can be called inside a callback.
This commit is contained in:
committed by
Sebastian Markbage
parent
f514662ca0
commit
0ca1cea26a
@@ -32,12 +32,16 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
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
|
||||
|
||||
@@ -132,14 +132,14 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
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:
|
||||
|
||||
@@ -144,9 +144,6 @@ module.exports = function<T, P, I, C>(config : HostConfig<T, P, I, C>) {
|
||||
// to the system not to redo any work here.
|
||||
workInProgress.pendingProps = null;
|
||||
workInProgress.updateQueue = null;
|
||||
if (current) {
|
||||
current.updateQueue = null;
|
||||
}
|
||||
|
||||
const returnFiber = workInProgress.return;
|
||||
|
||||
|
||||
@@ -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 <div>{this.props.children}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Foo({ multiplier }) {
|
||||
return (
|
||||
<div>
|
||||
<Bar multiplier={multiplier} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function updater(state, props) {
|
||||
return { num: state.num * props.multiplier };
|
||||
}
|
||||
|
||||
function callback() {
|
||||
this.setState({ called: true });
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo multiplier={2} />);
|
||||
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({
|
||||
|
||||
Reference in New Issue
Block a user