Skip to content

Commit 0841d66

Browse files
committed
Always push updates to interleaved queue first
Interleaves updates (updates that are scheduled while another render is already is progress) go into a special queue that isn't applied until the end of the current render. They are transferred to the "real" queue at the beginning of the next render. Currently we check during `setState` whether an update should go directly onto the real queue or onto the special interleaved queue. The logic is subtle and it can lead to bugs if you mess it up, as in facebook#24400. Instead, this changes it to always go onto the interleaved queue. The behavior is the same but the logic is simpler. As a further step, we can also wait to update the `childLanes` until the end of the current render. I'll do this in the next step.
1 parent 8dc5de3 commit 0841d66

8 files changed

+66
-126
lines changed

packages/react-reconciler/src/ReactFiberHooks.new.js

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ import {
8787
requestUpdateLane,
8888
requestEventTime,
8989
markSkippedUpdateLanes,
90-
isInterleavedUpdate,
9190
} from './ReactFiberWorkLoop.new';
9291

9392
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
@@ -2337,30 +2336,18 @@ function enqueueUpdate<S, A>(
23372336
update: Update<S, A>,
23382337
lane: Lane,
23392338
) {
2340-
if (isInterleavedUpdate(fiber, lane)) {
2341-
const interleaved = queue.interleaved;
2342-
if (interleaved === null) {
2343-
// This is the first update. Create a circular list.
2344-
update.next = update;
2345-
// At the end of the current render, this queue's interleaved updates will
2346-
// be transferred to the pending queue.
2347-
pushInterleavedQueue(queue);
2348-
} else {
2349-
update.next = interleaved.next;
2350-
interleaved.next = update;
2351-
}
2352-
queue.interleaved = update;
2339+
const interleaved = queue.interleaved;
2340+
if (interleaved === null) {
2341+
// This is the first update. Create a circular list.
2342+
update.next = update;
2343+
// At the end of the current render, this queue's interleaved updates will
2344+
// be transferred to the pending queue.
2345+
pushInterleavedQueue(queue);
23532346
} else {
2354-
const pending = queue.pending;
2355-
if (pending === null) {
2356-
// This is the first update. Create a circular list.
2357-
update.next = update;
2358-
} else {
2359-
update.next = pending.next;
2360-
pending.next = update;
2361-
}
2362-
queue.pending = update;
2347+
update.next = interleaved.next;
2348+
interleaved.next = update;
23632349
}
2350+
queue.interleaved = update;
23642351
}
23652352

23662353
function entangleTransitionUpdate<S, A>(

packages/react-reconciler/src/ReactFiberHooks.old.js

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ import {
8787
requestUpdateLane,
8888
requestEventTime,
8989
markSkippedUpdateLanes,
90-
isInterleavedUpdate,
9190
} from './ReactFiberWorkLoop.old';
9291

9392
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
@@ -2337,30 +2336,18 @@ function enqueueUpdate<S, A>(
23372336
update: Update<S, A>,
23382337
lane: Lane,
23392338
) {
2340-
if (isInterleavedUpdate(fiber, lane)) {
2341-
const interleaved = queue.interleaved;
2342-
if (interleaved === null) {
2343-
// This is the first update. Create a circular list.
2344-
update.next = update;
2345-
// At the end of the current render, this queue's interleaved updates will
2346-
// be transferred to the pending queue.
2347-
pushInterleavedQueue(queue);
2348-
} else {
2349-
update.next = interleaved.next;
2350-
interleaved.next = update;
2351-
}
2352-
queue.interleaved = update;
2339+
const interleaved = queue.interleaved;
2340+
if (interleaved === null) {
2341+
// This is the first update. Create a circular list.
2342+
update.next = update;
2343+
// At the end of the current render, this queue's interleaved updates will
2344+
// be transferred to the pending queue.
2345+
pushInterleavedQueue(queue);
23532346
} else {
2354-
const pending = queue.pending;
2355-
if (pending === null) {
2356-
// This is the first update. Create a circular list.
2357-
update.next = update;
2358-
} else {
2359-
update.next = pending.next;
2360-
pending.next = update;
2361-
}
2362-
queue.pending = update;
2347+
update.next = interleaved.next;
2348+
interleaved.next = update;
23632349
}
2350+
queue.interleaved = update;
23642351
}
23652352

23662353
function entangleTransitionUpdate<S, A>(

packages/react-reconciler/src/ReactFiberInterleavedUpdates.new.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ export function pushInterleavedQueue(
2828
}
2929
}
3030

31-
export function hasInterleavedUpdates() {
32-
return interleavedQueues !== null;
33-
}
34-
3531
export function enqueueInterleavedUpdates() {
3632
// Transfer the interleaved updates onto the main queue. Each queue has a
3733
// `pending` field and an `interleaved` field. When they are not null, they

packages/react-reconciler/src/ReactFiberInterleavedUpdates.old.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ export function pushInterleavedQueue(
2828
}
2929
}
3030

31-
export function hasInterleavedUpdates() {
32-
return interleavedQueues !== null;
33-
}
34-
3531
export function enqueueInterleavedUpdates() {
3632
// Transfer the interleaved updates onto the main queue. Each queue has a
3733
// `pending` field and an `interleaved` field. When they are not null, they

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,7 @@ import {
195195
pop as popFromStack,
196196
createCursor,
197197
} from './ReactFiberStack.new';
198-
import {
199-
enqueueInterleavedUpdates,
200-
hasInterleavedUpdates,
201-
} from './ReactFiberInterleavedUpdates.new';
198+
import {enqueueInterleavedUpdates} from './ReactFiberInterleavedUpdates.new';
202199

203200
import {
204201
markNestedUpdateScheduled,
@@ -606,8 +603,6 @@ export function scheduleUpdateOnFiber(
606603
}
607604

608605
if (root === workInProgressRoot) {
609-
// TODO: Consolidate with `isInterleavedUpdate` check
610-
611606
// Received an update to a tree that's in the middle of rendering. Mark
612607
// that there was an interleaved update work on this root. Unless the
613608
// `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
@@ -721,25 +716,15 @@ function markUpdateLaneFromFiberToRoot(
721716
}
722717
}
723718

724-
export function isInterleavedUpdate(fiber: Fiber, lane: Lane) {
719+
export function isUnsafeClassRenderPhaseUpdate(fiber: Fiber) {
720+
// Check if this is a render phase update. Only called by class components,
721+
// which special (deprecated) behavior for UNSAFE_componentWillReceive props.
725722
return (
726-
// TODO: Optimize slightly by comparing to root that fiber belongs to.
727-
// Requires some refactoring. Not a big deal though since it's rare for
728-
// concurrent apps to have more than a single root.
729-
(workInProgressRoot !== null ||
730-
// If the interleaved updates queue hasn't been cleared yet, then
731-
// we should treat this as an interleaved update, too. This is also a
732-
// defensive coding measure in case a new update comes in between when
733-
// rendering has finished and when the interleaved updates are transferred
734-
// to the main queue.
735-
hasInterleavedUpdates()) &&
736-
(fiber.mode & ConcurrentMode) !== NoMode &&
737-
// If this is a render phase update (i.e. UNSAFE_componentWillReceiveProps),
738-
// then don't treat this as an interleaved update. This pattern is
739-
// accompanied by a warning but we haven't fully deprecated it yet. We can
740-
// remove once the deferRenderPhaseUpdateToNextBatch flag is enabled.
741-
(deferRenderPhaseUpdateToNextBatch ||
742-
(executionContext & RenderContext) === NoContext)
723+
// TODO: Remove outdated deferRenderPhaseUpdateToNextBatch experiment. We
724+
// decided not to enable it.
725+
(!deferRenderPhaseUpdateToNextBatch ||
726+
(fiber.mode & ConcurrentMode) === NoMode) &&
727+
(executionContext & RenderContext) !== NoContext
743728
);
744729
}
745730

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,7 @@ import {
195195
pop as popFromStack,
196196
createCursor,
197197
} from './ReactFiberStack.old';
198-
import {
199-
enqueueInterleavedUpdates,
200-
hasInterleavedUpdates,
201-
} from './ReactFiberInterleavedUpdates.old';
198+
import {enqueueInterleavedUpdates} from './ReactFiberInterleavedUpdates.old';
202199

203200
import {
204201
markNestedUpdateScheduled,
@@ -606,8 +603,6 @@ export function scheduleUpdateOnFiber(
606603
}
607604

608605
if (root === workInProgressRoot) {
609-
// TODO: Consolidate with `isInterleavedUpdate` check
610-
611606
// Received an update to a tree that's in the middle of rendering. Mark
612607
// that there was an interleaved update work on this root. Unless the
613608
// `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render
@@ -721,25 +716,15 @@ function markUpdateLaneFromFiberToRoot(
721716
}
722717
}
723718

724-
export function isInterleavedUpdate(fiber: Fiber, lane: Lane) {
719+
export function isUnsafeClassRenderPhaseUpdate(fiber: Fiber) {
720+
// Check if this is a render phase update. Only called by class components,
721+
// which special (deprecated) behavior for UNSAFE_componentWillReceive props.
725722
return (
726-
// TODO: Optimize slightly by comparing to root that fiber belongs to.
727-
// Requires some refactoring. Not a big deal though since it's rare for
728-
// concurrent apps to have more than a single root.
729-
(workInProgressRoot !== null ||
730-
// If the interleaved updates queue hasn't been cleared yet, then
731-
// we should treat this as an interleaved update, too. This is also a
732-
// defensive coding measure in case a new update comes in between when
733-
// rendering has finished and when the interleaved updates are transferred
734-
// to the main queue.
735-
hasInterleavedUpdates()) &&
736-
(fiber.mode & ConcurrentMode) !== NoMode &&
737-
// If this is a render phase update (i.e. UNSAFE_componentWillReceiveProps),
738-
// then don't treat this as an interleaved update. This pattern is
739-
// accompanied by a warning but we haven't fully deprecated it yet. We can
740-
// remove once the deferRenderPhaseUpdateToNextBatch flag is enabled.
741-
(deferRenderPhaseUpdateToNextBatch ||
742-
(executionContext & RenderContext) === NoContext)
723+
// TODO: Remove outdated deferRenderPhaseUpdateToNextBatch experiment. We
724+
// decided not to enable it.
725+
(!deferRenderPhaseUpdateToNextBatch ||
726+
(fiber.mode & ConcurrentMode) === NoMode) &&
727+
(executionContext & RenderContext) !== NoContext
743728
);
744729
}
745730

packages/react-reconciler/src/ReactUpdateQueue.new.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags
107107
import {StrictLegacyMode} from './ReactTypeOfMode';
108108
import {
109109
markSkippedUpdateLanes,
110-
isInterleavedUpdate,
110+
isUnsafeClassRenderPhaseUpdate,
111111
} from './ReactFiberWorkLoop.new';
112112
import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.new';
113113
import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.new';
@@ -223,7 +223,19 @@ export function enqueueUpdate<State>(
223223

224224
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
225225

226-
if (isInterleavedUpdate(fiber, lane)) {
226+
if (isUnsafeClassRenderPhaseUpdate(fiber)) {
227+
// This is an unsafe render phase update. Add directly to the update
228+
// queue so we can process it immediately during the current render.
229+
const pending = sharedQueue.pending;
230+
if (pending === null) {
231+
// This is the first update. Create a circular list.
232+
update.next = update;
233+
} else {
234+
update.next = pending.next;
235+
pending.next = update;
236+
}
237+
sharedQueue.pending = update;
238+
} else {
227239
const interleaved = sharedQueue.interleaved;
228240
if (interleaved === null) {
229241
// This is the first update. Create a circular list.
@@ -236,16 +248,6 @@ export function enqueueUpdate<State>(
236248
interleaved.next = update;
237249
}
238250
sharedQueue.interleaved = update;
239-
} else {
240-
const pending = sharedQueue.pending;
241-
if (pending === null) {
242-
// This is the first update. Create a circular list.
243-
update.next = update;
244-
} else {
245-
update.next = pending.next;
246-
pending.next = update;
247-
}
248-
sharedQueue.pending = update;
249251
}
250252

251253
if (__DEV__) {

packages/react-reconciler/src/ReactUpdateQueue.old.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags
107107
import {StrictLegacyMode} from './ReactTypeOfMode';
108108
import {
109109
markSkippedUpdateLanes,
110-
isInterleavedUpdate,
110+
isUnsafeClassRenderPhaseUpdate,
111111
} from './ReactFiberWorkLoop.old';
112112
import {pushInterleavedQueue} from './ReactFiberInterleavedUpdates.old';
113113
import {setIsStrictModeForDevtools} from './ReactFiberDevToolsHook.old';
@@ -223,7 +223,19 @@ export function enqueueUpdate<State>(
223223

224224
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
225225

226-
if (isInterleavedUpdate(fiber, lane)) {
226+
if (isUnsafeClassRenderPhaseUpdate(fiber)) {
227+
// This is an unsafe render phase update. Add directly to the update
228+
// queue so we can process it immediately during the current render.
229+
const pending = sharedQueue.pending;
230+
if (pending === null) {
231+
// This is the first update. Create a circular list.
232+
update.next = update;
233+
} else {
234+
update.next = pending.next;
235+
pending.next = update;
236+
}
237+
sharedQueue.pending = update;
238+
} else {
227239
const interleaved = sharedQueue.interleaved;
228240
if (interleaved === null) {
229241
// This is the first update. Create a circular list.
@@ -236,16 +248,6 @@ export function enqueueUpdate<State>(
236248
interleaved.next = update;
237249
}
238250
sharedQueue.interleaved = update;
239-
} else {
240-
const pending = sharedQueue.pending;
241-
if (pending === null) {
242-
// This is the first update. Create a circular list.
243-
update.next = update;
244-
} else {
245-
update.next = pending.next;
246-
pending.next = update;
247-
}
248-
sharedQueue.pending = update;
249251
}
250252

251253
if (__DEV__) {

0 commit comments

Comments
 (0)