Skip to content

Commit 7bf40e1

Browse files
authored
Initialize update queue object on mount (#17560)
* Refactor Update Queues to Fix Rebasing Bug Fixes a bug related to rebasing updates. Once an update has committed, it should never un-commit, even if interrupted by a higher priority update. The fix includes a refactor of how update queues work. This commit is a combination of two PRs: - #17483 by @sebmarkbage refactors the hook update queue - #17510 by @acdlite refactors the class and root update queue Landing one without the other would cause state updates to sometimes be inconsistent across components, so I've combined them into a single commit in case they need to be reverted. Co-authored-by: Sebastian Markbåge <[email protected]> Co-authored-by: Andrew Clark <[email protected]> * Initialize update queue object on mount Instead of lazily initializing update queue objects on the first update, class and host root queues are created on mount. This simplifies the logic for appending new updates and matches what we do for hooks.
1 parent 031a5aa commit 7bf40e1

8 files changed

+530
-442
lines changed

packages/react-noop-renderer/src/createReactNoop.js

+24-11
Original file line numberDiff line numberDiff line change
@@ -1142,20 +1142,33 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
11421142

11431143
function logUpdateQueue(updateQueue: UpdateQueue<mixed>, depth) {
11441144
log(' '.repeat(depth + 1) + 'QUEUED UPDATES');
1145-
const firstUpdate = updateQueue.firstUpdate;
1146-
if (!firstUpdate) {
1145+
const last = updateQueue.baseQueue;
1146+
if (last === null) {
11471147
return;
11481148
}
1149+
const first = last.next;
1150+
let update = first;
1151+
if (update !== null) {
1152+
do {
1153+
log(
1154+
' '.repeat(depth + 1) + '~',
1155+
'[' + update.expirationTime + ']',
1156+
);
1157+
} while (update !== null && update !== first);
1158+
}
11491159

1150-
log(
1151-
' '.repeat(depth + 1) + '~',
1152-
'[' + firstUpdate.expirationTime + ']',
1153-
);
1154-
while (firstUpdate.next) {
1155-
log(
1156-
' '.repeat(depth + 1) + '~',
1157-
'[' + firstUpdate.expirationTime + ']',
1158-
);
1160+
const lastPending = updateQueue.shared.pending;
1161+
if (lastPending !== null) {
1162+
const firstPending = lastPending.next;
1163+
let pendingUpdate = firstPending;
1164+
if (pendingUpdate !== null) {
1165+
do {
1166+
log(
1167+
' '.repeat(depth + 1) + '~',
1168+
'[' + pendingUpdate.expirationTime + ']',
1169+
);
1170+
} while (pendingUpdate !== null && pendingUpdate !== firstPending);
1171+
}
11591172
}
11601173
}
11611174

packages/react-reconciler/src/ReactFiberBeginWork.js

+10-9
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,11 @@ import {
9090
reconcileChildFibers,
9191
cloneChildFibers,
9292
} from './ReactChildFiber';
93-
import {processUpdateQueue} from './ReactUpdateQueue';
93+
import {
94+
processUpdateQueue,
95+
cloneUpdateQueue,
96+
initializeUpdateQueue,
97+
} from './ReactUpdateQueue';
9498
import {
9599
NoWork,
96100
Never,
@@ -904,21 +908,16 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
904908
pushHostRootContext(workInProgress);
905909
const updateQueue = workInProgress.updateQueue;
906910
invariant(
907-
updateQueue !== null,
911+
current !== null && updateQueue !== null,
908912
'If the root does not have an updateQueue, we should have already ' +
909913
'bailed out. This error is likely caused by a bug in React. Please ' +
910914
'file an issue.',
911915
);
912916
const nextProps = workInProgress.pendingProps;
913917
const prevState = workInProgress.memoizedState;
914918
const prevChildren = prevState !== null ? prevState.element : null;
915-
processUpdateQueue(
916-
workInProgress,
917-
updateQueue,
918-
nextProps,
919-
null,
920-
renderExpirationTime,
921-
);
919+
cloneUpdateQueue(current, workInProgress);
920+
processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
922921
const nextState = workInProgress.memoizedState;
923922
// Caution: React DevTools currently depends on this property
924923
// being called "element".
@@ -1338,6 +1337,8 @@ function mountIndeterminateComponent(
13381337
workInProgress.memoizedState =
13391338
value.state !== null && value.state !== undefined ? value.state : null;
13401339

1340+
initializeUpdateQueue(workInProgress);
1341+
13411342
const getDerivedStateFromProps = Component.getDerivedStateFromProps;
13421343
if (typeof getDerivedStateFromProps === 'function') {
13431344
applyDerivedStateFromProps(

packages/react-reconciler/src/ReactFiberClassComponent.js

+23-46
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import type {Fiber} from './ReactFiber';
1111
import type {ExpirationTime} from './ReactFiberExpirationTime';
12+
import type {UpdateQueue} from './ReactUpdateQueue';
1213

1314
import React from 'react';
1415
import {Update, Snapshot} from 'shared/ReactSideEffectTags';
@@ -38,6 +39,8 @@ import {
3839
createUpdate,
3940
ReplaceState,
4041
ForceUpdate,
42+
initializeUpdateQueue,
43+
cloneUpdateQueue,
4144
} from './ReactUpdateQueue';
4245
import {NoWork} from './ReactFiberExpirationTime';
4346
import {
@@ -171,8 +174,9 @@ export function applyDerivedStateFromProps(
171174

172175
// Once the update queue is empty, persist the derived state onto the
173176
// base state.
174-
const updateQueue = workInProgress.updateQueue;
175-
if (updateQueue !== null && workInProgress.expirationTime === NoWork) {
177+
if (workInProgress.expirationTime === NoWork) {
178+
// Queue is always non-null for classes
179+
const updateQueue: UpdateQueue<any> = (workInProgress.updateQueue: any);
176180
updateQueue.baseState = memoizedState;
177181
}
178182
}
@@ -789,6 +793,8 @@ function mountClassInstance(
789793
instance.state = workInProgress.memoizedState;
790794
instance.refs = emptyRefsObject;
791795

796+
initializeUpdateQueue(workInProgress);
797+
792798
const contextType = ctor.contextType;
793799
if (typeof contextType === 'object' && contextType !== null) {
794800
instance.context = readContext(contextType);
@@ -829,17 +835,8 @@ function mountClassInstance(
829835
}
830836
}
831837

832-
let updateQueue = workInProgress.updateQueue;
833-
if (updateQueue !== null) {
834-
processUpdateQueue(
835-
workInProgress,
836-
updateQueue,
837-
newProps,
838-
instance,
839-
renderExpirationTime,
840-
);
841-
instance.state = workInProgress.memoizedState;
842-
}
838+
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
839+
instance.state = workInProgress.memoizedState;
843840

844841
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
845842
if (typeof getDerivedStateFromProps === 'function') {
@@ -863,17 +860,13 @@ function mountClassInstance(
863860
callComponentWillMount(workInProgress, instance);
864861
// If we had additional state updates during this life-cycle, let's
865862
// process them now.
866-
updateQueue = workInProgress.updateQueue;
867-
if (updateQueue !== null) {
868-
processUpdateQueue(
869-
workInProgress,
870-
updateQueue,
871-
newProps,
872-
instance,
873-
renderExpirationTime,
874-
);
875-
instance.state = workInProgress.memoizedState;
876-
}
863+
processUpdateQueue(
864+
workInProgress,
865+
newProps,
866+
instance,
867+
renderExpirationTime,
868+
);
869+
instance.state = workInProgress.memoizedState;
877870
}
878871

879872
if (typeof instance.componentDidMount === 'function') {
@@ -936,17 +929,8 @@ function resumeMountClassInstance(
936929

937930
const oldState = workInProgress.memoizedState;
938931
let newState = (instance.state = oldState);
939-
let updateQueue = workInProgress.updateQueue;
940-
if (updateQueue !== null) {
941-
processUpdateQueue(
942-
workInProgress,
943-
updateQueue,
944-
newProps,
945-
instance,
946-
renderExpirationTime,
947-
);
948-
newState = workInProgress.memoizedState;
949-
}
932+
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
933+
newState = workInProgress.memoizedState;
950934
if (
951935
oldProps === newProps &&
952936
oldState === newState &&
@@ -1035,6 +1019,8 @@ function updateClassInstance(
10351019
): boolean {
10361020
const instance = workInProgress.stateNode;
10371021

1022+
cloneUpdateQueue(current, workInProgress);
1023+
10381024
const oldProps = workInProgress.memoizedProps;
10391025
instance.props =
10401026
workInProgress.type === workInProgress.elementType
@@ -1081,17 +1067,8 @@ function updateClassInstance(
10811067

10821068
const oldState = workInProgress.memoizedState;
10831069
let newState = (instance.state = oldState);
1084-
let updateQueue = workInProgress.updateQueue;
1085-
if (updateQueue !== null) {
1086-
processUpdateQueue(
1087-
workInProgress,
1088-
updateQueue,
1089-
newProps,
1090-
instance,
1091-
renderExpirationTime,
1092-
);
1093-
newState = workInProgress.memoizedState;
1094-
}
1070+
processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
1071+
newState = workInProgress.memoizedState;
10951072

10961073
if (
10971074
oldProps === newProps &&

0 commit comments

Comments
 (0)