Use global state for hasForceUpdate instead of persisting to queue (#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
This commit is contained in:
Andrew Clark
2018-05-14 19:18:47 -07:00
committed by GitHub
parent 8c747d01cb
commit 73f59e6f31
3 changed files with 67 additions and 39 deletions
+28 -32
View File
@@ -32,6 +32,8 @@ import {StrictMode} from './ReactTypeOfMode';
import {
enqueueUpdate,
processUpdateQueue,
checkHasForceUpdateAfterProcessing,
resetHasForceUpdateBeforeProcessing,
createUpdate,
ReplaceState,
ForceUpdate,
@@ -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') {
@@ -789,6 +783,8 @@ export default function(
}
}
resetHasForceUpdateBeforeProcessing();
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
@@ -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.
@@ -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,
@@ -922,6 +917,8 @@ export default function(
}
}
resetHasForceUpdateBeforeProcessing();
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
let updateQueue = workInProgress.updateQueue;
@@ -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.
@@ -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,
+16 -7
View File
@@ -131,9 +131,6 @@ 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;
@@ -141,6 +138,11 @@ 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;
@@ -164,7 +166,6 @@ export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null,
hasForceUpdate: false,
};
return queue;
}
@@ -183,8 +184,6 @@ function cloneUpdateQueue<State>(
firstCapturedUpdate: null,
lastCapturedUpdate: null,
hasForceUpdate: false,
firstEffect: null,
lastEffect: null,
@@ -423,7 +422,7 @@ function getStateFromUpdate<State>(
return Object.assign({}, prevState, partialState);
}
case ForceUpdate: {
queue.hasForceUpdate = true;
hasForceUpdate = true;
return prevState;
}
}
@@ -437,6 +436,8 @@ export function processUpdateQueue<State>(
instance: any,
renderExpirationTime: ExpirationTime,
): void {
hasForceUpdate = false;
if (
queue.expirationTime === NoWork ||
queue.expirationTime > renderExpirationTime
@@ -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>,
@@ -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 = [];