Skip to content

Commit

Permalink
Use global state for hasForceUpdate instead of persisting to queue (#…
Browse files Browse the repository at this point in the history
…12808)

* Use global state for `hasForceUpdate` instead of persisting to queue

Fixes a bug where `hasForceUpdate` was not reset on commit.

Ideally we'd use a tuple and return `hasForceUpdate` from
`processUpdateQueue`.

* Remove underscore and add comment

* Remove temporary variables
  • Loading branch information
acdlite authored May 15, 2018
1 parent 8c747d0 commit 73f59e6
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 39 deletions.
60 changes: 28 additions & 32 deletions packages/react-reconciler/src/ReactFiberClassComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {StrictMode} from './ReactTypeOfMode';
import {
enqueueUpdate,
processUpdateQueue,
checkHasForceUpdateAfterProcessing,
resetHasForceUpdateBeforeProcessing,
createUpdate,
ReplaceState,
ForceUpdate,
Expand Down Expand Up @@ -235,14 +237,6 @@ export default function(
newState,
newContext,
) {
if (
workInProgress.updateQueue !== null &&
workInProgress.updateQueue.hasForceUpdate
) {
// If forceUpdate was called, disregard sCU.
return true;
}

const instance = workInProgress.stateNode;
const ctor = workInProgress.type;
if (typeof instance.shouldComponentUpdate === 'function') {
Expand Down Expand Up @@ -789,6 +783,8 @@ export default function(
}
}

resetHasForceUpdateBeforeProcessing();

const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
Expand All @@ -806,10 +802,7 @@ export default function(
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!(
workInProgress.updateQueue !== null &&
workInProgress.updateQueue.hasForceUpdate
)
!checkHasForceUpdateAfterProcessing()
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
Expand All @@ -828,14 +821,16 @@ export default function(
newState = workInProgress.memoizedState;
}

const shouldUpdate = checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
);
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
);

if (shouldUpdate) {
// In order to support react-lifecycles-compat polyfilled components,
Expand Down Expand Up @@ -922,6 +917,8 @@ export default function(
}
}

resetHasForceUpdateBeforeProcessing();

const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
Expand All @@ -940,10 +937,7 @@ export default function(
oldProps === newProps &&
oldState === newState &&
!hasContextChanged() &&
!(
workInProgress.updateQueue !== null &&
workInProgress.updateQueue.hasForceUpdate
)
!checkHasForceUpdateAfterProcessing()
) {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
Expand Down Expand Up @@ -977,14 +971,16 @@ export default function(
}
}

const shouldUpdate = checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
);
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
oldProps,
newProps,
oldState,
newState,
newContext,
);

if (shouldUpdate) {
// In order to support react-lifecycles-compat polyfilled components,
Expand Down
23 changes: 16 additions & 7 deletions packages/react-reconciler/src/ReactUpdateQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,18 @@ export type UpdateQueue<State> = {

firstCapturedEffect: Update<State> | null,
lastCapturedEffect: Update<State> | null,

// TODO: Workaround for lack of tuples. Could use global state instead.
hasForceUpdate: boolean,
};

export const UpdateState = 0;
export const ReplaceState = 1;
export const ForceUpdate = 2;
export const CaptureUpdate = 3;

// Global state that is reset at the beginning of calling `processUpdateQueue`.
// It should only be read right after calling `processUpdateQueue`, via
// `checkHasForceUpdateAfterProcessing`.
let hasForceUpdate = false;

let didWarnUpdateInsideUpdate;
let currentlyProcessingQueue;
export let resetCurrentlyProcessingQueue;
Expand All @@ -164,7 +166,6 @@ export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null,
hasForceUpdate: false,
};
return queue;
}
Expand All @@ -183,8 +184,6 @@ function cloneUpdateQueue<State>(
firstCapturedUpdate: null,
lastCapturedUpdate: null,

hasForceUpdate: false,

firstEffect: null,
lastEffect: null,

Expand Down Expand Up @@ -423,7 +422,7 @@ function getStateFromUpdate<State>(
return Object.assign({}, prevState, partialState);
}
case ForceUpdate: {
queue.hasForceUpdate = true;
hasForceUpdate = true;
return prevState;
}
}
Expand All @@ -437,6 +436,8 @@ export function processUpdateQueue<State>(
instance: any,
renderExpirationTime: ExpirationTime,
): void {
hasForceUpdate = false;

if (
queue.expirationTime === NoWork ||
queue.expirationTime > renderExpirationTime
Expand Down Expand Up @@ -595,6 +596,14 @@ function callCallback(callback, context) {
callback.call(context);
}

export function resetHasForceUpdateBeforeProcessing() {
hasForceUpdate = false;
}

export function checkHasForceUpdateAfterProcessing(): boolean {
return hasForceUpdate;
}

export function commitUpdateQueue<State>(
finishedWork: Fiber,
finishedQueue: UpdateQueue<State>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,29 @@ describe('ReactIncremental', () => {
expect(ops).toEqual(['Foo', 'Bar', 'Baz', 'Bar', 'Baz']);
});

it('should clear forceUpdate after update is flushed', () => {
let a = 0;

class Foo extends React.PureComponent {
render() {
const msg = `A: ${a}, B: ${this.props.b}`;
ReactNoop.yield(msg);
return msg;
}
}

const foo = React.createRef(null);
ReactNoop.render(<Foo ref={foo} b={0} />);
expect(ReactNoop.flush()).toEqual(['A: 0, B: 0']);

a = 1;
foo.current.forceUpdate();
expect(ReactNoop.flush()).toEqual(['A: 1, B: 0']);

ReactNoop.render(<Foo ref={foo} b={0} />);
expect(ReactNoop.flush()).toEqual([]);
});

xit('can call sCU while resuming a partly mounted component', () => {
let ops = [];

Expand Down

0 comments on commit 73f59e6

Please sign in to comment.