Clean up timeoutMs-related implementation details (#19704)

* Disable busyDelayMs and busyMinDurationMs

Refer to explanation in previous commit.

* Remove unnecessary work loop variables

Since we no longer support SuspenseConfig options, we don't need to
track these values.

* Remove unnecessary Update fields
This commit is contained in:
Andrew Clark
2020-08-27 09:47:58 -07:00
committed by GitHub
parent 8da0da0937
commit 1eaafc9ade
17 changed files with 25 additions and 411 deletions
@@ -199,7 +199,7 @@ const classComponentUpdater = {
const suspenseConfig = requestCurrentSuspenseConfig();
const lane = requestUpdateLane(fiber, suspenseConfig);
const update = createUpdate(eventTime, lane, suspenseConfig);
const update = createUpdate(eventTime, lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
@@ -230,7 +230,7 @@ const classComponentUpdater = {
const suspenseConfig = requestCurrentSuspenseConfig();
const lane = requestUpdateLane(fiber, suspenseConfig);
const update = createUpdate(eventTime, lane, suspenseConfig);
const update = createUpdate(eventTime, lane);
update.tag = ReplaceState;
update.payload = payload;
@@ -263,7 +263,7 @@ const classComponentUpdater = {
const suspenseConfig = requestCurrentSuspenseConfig();
const lane = requestUpdateLane(fiber, suspenseConfig);
const update = createUpdate(eventTime, lane, suspenseConfig);
const update = createUpdate(eventTime, lane);
update.tag = ForceUpdate;
if (callback !== undefined && callback !== null) {
@@ -199,7 +199,7 @@ const classComponentUpdater = {
const suspenseConfig = requestCurrentSuspenseConfig();
const lane = requestUpdateLane(fiber, suspenseConfig);
const update = createUpdate(eventTime, lane, suspenseConfig);
const update = createUpdate(eventTime, lane);
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
@@ -230,7 +230,7 @@ const classComponentUpdater = {
const suspenseConfig = requestCurrentSuspenseConfig();
const lane = requestUpdateLane(fiber, suspenseConfig);
const update = createUpdate(eventTime, lane, suspenseConfig);
const update = createUpdate(eventTime, lane);
update.tag = ReplaceState;
update.payload = payload;
@@ -263,7 +263,7 @@ const classComponentUpdater = {
const suspenseConfig = requestCurrentSuspenseConfig();
const lane = requestUpdateLane(fiber, suspenseConfig);
const update = createUpdate(eventTime, lane, suspenseConfig);
const update = createUpdate(eventTime, lane);
update.tag = ForceUpdate;
if (callback !== undefined && callback !== null) {
@@ -63,7 +63,6 @@ import {
warnIfNotCurrentlyActingEffectsInDEV,
warnIfNotCurrentlyActingUpdatesInDev,
warnIfNotScopedWithMatchingAct,
markRenderEventTimeAndConfig,
markSkippedUpdateLanes,
} from './ReactFiberWorkLoop.new';
@@ -97,11 +96,7 @@ import {markStateUpdateScheduled} from './SchedulingProfiler';
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
type Update<S, A> = {|
// TODO: Temporary field. Will remove this by storing a map of
// transition -> start time on the root.
eventTime: number,
lane: Lane,
suspenseConfig: null | SuspenseConfig,
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null,
@@ -715,17 +710,13 @@ function updateReducer<S, I, A>(
let newBaseQueueLast = null;
let update = first;
do {
const suspenseConfig = update.suspenseConfig;
const updateLane = update.lane;
const updateEventTime = update.eventTime;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<S, A> = {
eventTime: updateEventTime,
lane: updateLane,
suspenseConfig: suspenseConfig,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
@@ -750,12 +741,10 @@ function updateReducer<S, I, A>(
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
eventTime: updateEventTime,
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
suspenseConfig: update.suspenseConfig,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
@@ -764,14 +753,6 @@ function updateReducer<S, I, A>(
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Mark the event time of this update as relevant to this render pass.
// TODO: This should ideally use the true event time of this update rather than
// its priority which is a derived and not reverseable value.
// TODO: We should skip this update if it was already committed but currently
// we have no way of detecting the difference between a committed and suspended
// update here.
markRenderEventTimeAndConfig(updateEventTime, suspenseConfig);
// Process this update.
if (update.eagerReducer === reducer) {
// If this update was processed eagerly, and its reducer matches the
@@ -1708,9 +1689,7 @@ function dispatchAction<S, A>(
const lane = requestUpdateLane(fiber, suspenseConfig);
const update: Update<S, A> = {
eventTime,
lane,
suspenseConfig,
action,
eagerReducer: null,
eagerState: null,
@@ -62,7 +62,6 @@ import {
warnIfNotCurrentlyActingEffectsInDEV,
warnIfNotCurrentlyActingUpdatesInDev,
warnIfNotScopedWithMatchingAct,
markRenderEventTimeAndConfig,
markSkippedUpdateLanes,
} from './ReactFiberWorkLoop.old';
@@ -96,11 +95,7 @@ import {markStateUpdateScheduled} from './SchedulingProfiler';
const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
type Update<S, A> = {|
// TODO: Temporary field. Will remove this by storing a map of
// transition -> start time on the root.
eventTime: number,
lane: Lane,
suspenseConfig: null | SuspenseConfig,
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null,
@@ -714,17 +709,13 @@ function updateReducer<S, I, A>(
let newBaseQueueLast = null;
let update = first;
do {
const suspenseConfig = update.suspenseConfig;
const updateLane = update.lane;
const updateEventTime = update.eventTime;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<S, A> = {
eventTime: updateEventTime,
lane: updateLane,
suspenseConfig: suspenseConfig,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
@@ -749,12 +740,10 @@ function updateReducer<S, I, A>(
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
eventTime: updateEventTime,
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
suspenseConfig: update.suspenseConfig,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
@@ -763,14 +752,6 @@ function updateReducer<S, I, A>(
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Mark the event time of this update as relevant to this render pass.
// TODO: This should ideally use the true event time of this update rather than
// its priority which is a derived and not reversible value.
// TODO: We should skip this update if it was already committed but currently
// we have no way of detecting the difference between a committed and suspended
// update here.
markRenderEventTimeAndConfig(updateEventTime, suspenseConfig);
// Process this update.
if (update.eagerReducer === reducer) {
// If this update was processed eagerly, and its reducer matches the
@@ -1706,9 +1687,7 @@ function dispatchAction<S, A>(
const lane = requestUpdateLane(fiber, suspenseConfig);
const update: Update<S, A> = {
eventTime,
lane,
suspenseConfig,
action,
eagerReducer: null,
eagerState: null,
+3
View File
@@ -461,6 +461,9 @@ export function includesNonIdleWork(lanes: Lanes) {
export function includesOnlyRetries(lanes: Lanes) {
return (lanes & RetryLanes) === lanes;
}
export function includesOnlyTransitions(lanes: Lanes) {
return (lanes & TransitionLanes) === lanes;
}
// To ensure consistency across multiple updates in the same event, this should
// be a pure function, so that it always returns the same lane for given inputs.
@@ -212,7 +212,6 @@ export function propagateContextChange(
const update = createUpdate(
NoTimestamp,
pickArbitraryLane(renderLanes),
null,
);
update.tag = ForceUpdate;
// TODO: Because we don't have a work-in-progress, this will add the
@@ -212,7 +212,6 @@ export function propagateContextChange(
const update = createUpdate(
NoTimestamp,
pickArbitraryLane(renderLanes),
null,
);
update.tag = ForceUpdate;
// TODO: Because we don't have a work-in-progress, this will add the
@@ -297,7 +297,7 @@ export function updateContainer(
}
}
const update = createUpdate(eventTime, lane, suspenseConfig);
const update = createUpdate(eventTime, lane);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
@@ -297,7 +297,7 @@ export function updateContainer(
}
}
const update = createUpdate(eventTime, lane, suspenseConfig);
const update = createUpdate(eventTime, lane);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
@@ -76,7 +76,7 @@ function createRootErrorUpdate(
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
const update = createUpdate(NoTimestamp, lane, null);
const update = createUpdate(NoTimestamp, lane);
// Unmount the root by rendering null.
update.tag = CaptureUpdate;
// Caution: React DevTools currently depends on this property
@@ -95,7 +95,7 @@ function createClassErrorUpdate(
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
const update = createUpdate(NoTimestamp, lane, null);
const update = createUpdate(NoTimestamp, lane);
update.tag = CaptureUpdate;
const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
if (typeof getDerivedStateFromError === 'function') {
@@ -274,7 +274,7 @@ function throwException(
// When we try rendering again, we should not reuse the current fiber,
// since it's known to be in an inconsistent state. Use a force update to
// prevent a bail out.
const update = createUpdate(NoTimestamp, SyncLane, null);
const update = createUpdate(NoTimestamp, SyncLane);
update.tag = ForceUpdate;
enqueueUpdate(sourceFiber, update);
}
@@ -76,7 +76,7 @@ function createRootErrorUpdate(
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
const update = createUpdate(NoTimestamp, lane, null);
const update = createUpdate(NoTimestamp, lane);
// Unmount the root by rendering null.
update.tag = CaptureUpdate;
// Caution: React DevTools currently depends on this property
@@ -95,7 +95,7 @@ function createClassErrorUpdate(
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
const update = createUpdate(NoTimestamp, lane, null);
const update = createUpdate(NoTimestamp, lane);
update.tag = CaptureUpdate;
const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
if (typeof getDerivedStateFromError === 'function') {
@@ -276,7 +276,7 @@ function throwException(
// When we try rendering again, we should not reuse the current fiber,
// since it's known to be in an inconsistent state. Use a force update to
// prevent a bail out.
const update = createUpdate(NoTimestamp, SyncLane, null);
const update = createUpdate(NoTimestamp, SyncLane);
update.tag = ForceUpdate;
enqueueUpdate(sourceFiber, update);
}
@@ -171,6 +171,7 @@ import {
hasDiscreteLanes,
includesNonIdleWork,
includesOnlyRetries,
includesOnlyTransitions,
getNextLanes,
returnNextLanesPriority,
setCurrentUpdateLanePriority,
@@ -302,8 +303,6 @@ const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
// A fatal error, if one is thrown
let workInProgressRootFatalError: mixed = null;
let workInProgressRootLatestSuspenseTimeout: number = NoTimestamp;
let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
// "Included" lanes refer to lanes that were worked on during this render. It's
// slightly different than `renderLanes` because `renderLanes` can change as you
// enter and exit an Offscreen tree. This value is the combination of all render
@@ -323,7 +322,6 @@ let mostRecentlyUpdatedRoot: FiberRoot | null = null;
// model where we don't commit new loading states in too quick succession.
let globalMostRecentFallbackTime: number = 0;
const FALLBACK_THROTTLE_MS: number = 500;
const DEFAULT_TIMEOUT_MS: number = 5000;
// The absolute time for when we should start giving up on rendering
// more and prefer CPU suspense heuristics instead.
@@ -923,12 +921,10 @@ function finishConcurrentRender(root, exitStatus, lanes) {
case RootSuspendedWithDelay: {
markRootSuspended(root, lanes);
if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) {
if (includesOnlyTransitions(lanes)) {
// This is a transition, so we should exit without committing a
// placeholder and without scheduling a timeout. Delay indefinitely
// until we receive more data.
// TODO: Check the lanes to see if it's a transition, instead of
// tracking the latest timeout.
break;
}
@@ -963,30 +959,6 @@ function finishConcurrentRender(root, exitStatus, lanes) {
}
case RootCompleted: {
// The work completed. Ready to commit.
const mostRecentEventTime = getMostRecentEventTime(root, lanes);
if (
// do not delay if we're inside an act() scope
!shouldForceFlushFallbacksInDEV() &&
mostRecentEventTime !== NoTimestamp &&
workInProgressRootCanSuspendUsingConfig !== null
) {
// If we have exceeded the minimum loading delay, which probably
// means we have shown a spinner already, we might have to suspend
// a bit longer to ensure that the spinner is shown for
// enough time.
const msUntilTimeout = computeMsUntilSuspenseLoadingDelay(
mostRecentEventTime,
workInProgressRootCanSuspendUsingConfig,
);
if (msUntilTimeout > 10) {
markRootSuspended(root, lanes);
root.timeoutHandle = scheduleTimeout(
commitRoot.bind(null, root),
msUntilTimeout,
);
break;
}
}
commitRoot(root);
break;
}
@@ -1369,8 +1341,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
workInProgressRootExitStatus = RootIncomplete;
workInProgressRootFatalError = null;
workInProgressRootLatestSuspenseTimeout = NoTimestamp;
workInProgressRootCanSuspendUsingConfig = null;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
@@ -1482,30 +1452,6 @@ export function markCommitTimeOfFallback() {
globalMostRecentFallbackTime = now();
}
export function markRenderEventTimeAndConfig(
eventTime: number,
suspenseConfig: null | SuspenseConfig,
): void {
// Track the largest/latest timeout deadline in this batch.
// TODO: If there are two transitions in the same batch, shouldn't we
// choose the smaller one? Maybe this is because when an intermediate
// transition is superseded, we should ignore its suspense config, but
// we don't currently.
if (suspenseConfig !== null) {
// If `timeoutMs` is not specified, we default to 5 seconds. We have to
// resolve this default here because `suspenseConfig` is owned
// by userspace.
// TODO: Store this on the root instead (transition -> timeoutMs)
// TODO: Should this default to a JND instead?
const timeoutMs = suspenseConfig.timeoutMs | 0 || DEFAULT_TIMEOUT_MS;
const timeoutTime = eventTime + timeoutMs;
if (timeoutTime > workInProgressRootLatestSuspenseTimeout) {
workInProgressRootLatestSuspenseTimeout = timeoutTime;
workInProgressRootCanSuspendUsingConfig = suspenseConfig;
}
}
}
export function markSkippedUpdateLanes(lane: Lane | Lanes): void {
workInProgressRootSkippedLanes = mergeLanes(
lane,
@@ -3079,30 +3025,6 @@ function jnd(timeElapsed: number) {
: ceil(timeElapsed / 1960) * 1960;
}
function computeMsUntilSuspenseLoadingDelay(
mostRecentEventTime: number,
suspenseConfig: SuspenseConfig,
) {
const busyMinDurationMs = (suspenseConfig.busyMinDurationMs: any) | 0;
if (busyMinDurationMs <= 0) {
return 0;
}
const busyDelayMs = (suspenseConfig.busyDelayMs: any) | 0;
// Compute the time until this render pass would expire.
const currentTimeMs: number = now();
const eventTimeMs: number = mostRecentEventTime;
const timeElapsed = currentTimeMs - eventTimeMs;
if (timeElapsed <= busyDelayMs) {
// If we haven't yet waited longer than the initial delay, we don't
// have to wait any additional time.
return 0;
}
const msUntilTimeout = busyDelayMs + busyMinDurationMs - timeElapsed;
// This is the value that is passed to `setTimeout`.
return msUntilTimeout;
}
function checkForNestedUpdates() {
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
@@ -157,6 +157,7 @@ import {
hasDiscreteLanes,
includesNonIdleWork,
includesOnlyRetries,
includesOnlyTransitions,
getNextLanes,
returnNextLanesPriority,
setCurrentUpdateLanePriority,
@@ -287,8 +288,6 @@ const subtreeRenderLanesCursor: StackCursor<Lanes> = createCursor(NoLanes);
let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;
// A fatal error, if one is thrown
let workInProgressRootFatalError: mixed = null;
let workInProgressRootLatestSuspenseTimeout: number = NoTimestamp;
let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;
// "Included" lanes refer to lanes that were worked on during this render. It's
// slightly different than `renderLanes` because `renderLanes` can change as you
// enter and exit an Offscreen tree. This value is the combination of all render
@@ -308,7 +307,6 @@ let mostRecentlyUpdatedRoot: FiberRoot | null = null;
// model where we don't commit new loading states in too quick succession.
let globalMostRecentFallbackTime: number = 0;
const FALLBACK_THROTTLE_MS: number = 500;
const DEFAULT_TIMEOUT_MS: number = 5000;
// The absolute time for when we should start giving up on rendering
// more and prefer CPU suspense heuristics instead.
@@ -911,12 +909,10 @@ function finishConcurrentRender(root, exitStatus, lanes) {
case RootSuspendedWithDelay: {
markRootSuspended(root, lanes);
if (workInProgressRootLatestSuspenseTimeout !== NoTimestamp) {
if (includesOnlyTransitions(lanes)) {
// This is a transition, so we should exit without committing a
// placeholder and without scheduling a timeout. Delay indefinitely
// until we receive more data.
// TODO: Check the lanes to see if it's a transition, instead of
// tracking the latest timeout.
break;
}
@@ -951,30 +947,6 @@ function finishConcurrentRender(root, exitStatus, lanes) {
}
case RootCompleted: {
// The work completed. Ready to commit.
const mostRecentEventTime = getMostRecentEventTime(root, lanes);
if (
// do not delay if we're inside an act() scope
!shouldForceFlushFallbacksInDEV() &&
mostRecentEventTime !== NoTimestamp &&
workInProgressRootCanSuspendUsingConfig !== null
) {
// If we have exceeded the minimum loading delay, which probably
// means we have shown a spinner already, we might have to suspend
// a bit longer to ensure that the spinner is shown for
// enough time.
const msUntilTimeout = computeMsUntilSuspenseLoadingDelay(
mostRecentEventTime,
workInProgressRootCanSuspendUsingConfig,
);
if (msUntilTimeout > 10) {
markRootSuspended(root, lanes);
root.timeoutHandle = scheduleTimeout(
commitRoot.bind(null, root),
msUntilTimeout,
);
break;
}
}
commitRoot(root);
break;
}
@@ -1357,8 +1329,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes) {
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
workInProgressRootExitStatus = RootIncomplete;
workInProgressRootFatalError = null;
workInProgressRootLatestSuspenseTimeout = NoTimestamp;
workInProgressRootCanSuspendUsingConfig = null;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
@@ -1470,30 +1440,6 @@ export function markCommitTimeOfFallback() {
globalMostRecentFallbackTime = now();
}
export function markRenderEventTimeAndConfig(
eventTime: number,
suspenseConfig: null | SuspenseConfig,
): void {
// Track the largest/latest timeout deadline in this batch.
// TODO: If there are two transitions in the same batch, shouldn't we
// choose the smaller one? Maybe this is because when an intermediate
// transition is superseded, we should ignore its suspense config, but
// we don't currently.
if (suspenseConfig !== null) {
// If `timeoutMs` is not specified, we default to 5 seconds. We have to
// resolve this default here because `suspenseConfig` is owned
// by userspace.
// TODO: Store this on the root instead (transition -> timeoutMs)
// TODO: Should this default to a JND instead?
const timeoutMs = suspenseConfig.timeoutMs | 0 || DEFAULT_TIMEOUT_MS;
const timeoutTime = eventTime + timeoutMs;
if (timeoutTime > workInProgressRootLatestSuspenseTimeout) {
workInProgressRootLatestSuspenseTimeout = timeoutTime;
workInProgressRootCanSuspendUsingConfig = suspenseConfig;
}
}
}
export function markSkippedUpdateLanes(lane: Lane | Lanes): void {
workInProgressRootSkippedLanes = mergeLanes(
lane,
@@ -3025,30 +2971,6 @@ function jnd(timeElapsed: number) {
: ceil(timeElapsed / 1960) * 1960;
}
function computeMsUntilSuspenseLoadingDelay(
mostRecentEventTime: number,
suspenseConfig: SuspenseConfig,
) {
const busyMinDurationMs = (suspenseConfig.busyMinDurationMs: any) | 0;
if (busyMinDurationMs <= 0) {
return 0;
}
const busyDelayMs = (suspenseConfig.busyDelayMs: any) | 0;
// Compute the time until this render pass would expire.
const currentTimeMs: number = now();
const eventTimeMs: number = mostRecentEventTime;
const timeElapsed = currentTimeMs - eventTimeMs;
if (timeElapsed <= busyDelayMs) {
// If we haven't yet waited longer than the initial delay, we don't
// have to wait any additional time.
return 0;
}
const msUntilTimeout = busyDelayMs + busyMinDurationMs - timeElapsed;
// This is the value that is passed to `setTimeout`.
return msUntilTimeout;
}
function checkForNestedUpdates() {
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
@@ -86,7 +86,6 @@
import type {Fiber} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane';
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
import {NoLane, NoLanes, isSubsetOfLanes, mergeLanes} from './ReactFiberLane';
import {
@@ -98,10 +97,7 @@ import {Callback, ShouldCapture, DidCapture} from './ReactSideEffectTags';
import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
import {StrictMode} from './ReactTypeOfMode';
import {
markRenderEventTimeAndConfig,
markSkippedUpdateLanes,
} from './ReactFiberWorkLoop.new';
import {markSkippedUpdateLanes} from './ReactFiberWorkLoop.new';
import invariant from 'shared/invariant';
@@ -112,7 +108,6 @@ export type Update<State> = {|
// transition -> event time on the root.
eventTime: number,
lane: Lane,
suspenseConfig: null | SuspenseConfig,
tag: 0 | 1 | 2 | 3,
payload: any,
@@ -186,15 +181,10 @@ export function cloneUpdateQueue<State>(
}
}
export function createUpdate(
eventTime: number,
lane: Lane,
suspenseConfig: null | SuspenseConfig,
): Update<*> {
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
const update: Update<*> = {
eventTime,
lane,
suspenseConfig,
tag: UpdateState,
payload: null,
@@ -269,7 +259,6 @@ export function enqueueCapturedUpdate<State>(
const clone: Update<State> = {
eventTime: update.eventTime,
lane: update.lane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
@@ -482,7 +471,6 @@ export function processUpdateQueue<State>(
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
@@ -508,7 +496,6 @@ export function processUpdateQueue<State>(
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
@@ -519,14 +506,6 @@ export function processUpdateQueue<State>(
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Mark the event time of this update as relevant to this render pass.
// TODO: This should ideally use the true event time of this update rather than
// its priority which is a derived and not reversible value.
// TODO: We should skip this update if it was already committed but currently
// we have no way of detecting the difference between a committed and suspended
// update here.
markRenderEventTimeAndConfig(updateEventTime, update.suspenseConfig);
// Process this update.
newState = getStateFromUpdate(
workInProgress,
@@ -86,7 +86,6 @@
import type {Fiber} from './ReactInternalTypes';
import type {Lanes, Lane} from './ReactFiberLane';
import type {SuspenseConfig} from './ReactFiberSuspenseConfig';
import {NoLane, NoLanes, isSubsetOfLanes, mergeLanes} from './ReactFiberLane';
import {
@@ -98,10 +97,7 @@ import {Callback, ShouldCapture, DidCapture} from './ReactSideEffectTags';
import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
import {StrictMode} from './ReactTypeOfMode';
import {
markRenderEventTimeAndConfig,
markSkippedUpdateLanes,
} from './ReactFiberWorkLoop.old';
import {markSkippedUpdateLanes} from './ReactFiberWorkLoop.old';
import invariant from 'shared/invariant';
@@ -112,7 +108,6 @@ export type Update<State> = {|
// transition -> event time on the root.
eventTime: number,
lane: Lane,
suspenseConfig: null | SuspenseConfig,
tag: 0 | 1 | 2 | 3,
payload: any,
@@ -186,15 +181,10 @@ export function cloneUpdateQueue<State>(
}
}
export function createUpdate(
eventTime: number,
lane: Lane,
suspenseConfig: null | SuspenseConfig,
): Update<*> {
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
const update: Update<*> = {
eventTime,
lane,
suspenseConfig,
tag: UpdateState,
payload: null,
@@ -269,7 +259,6 @@ export function enqueueCapturedUpdate<State>(
const clone: Update<State> = {
eventTime: update.eventTime,
lane: update.lane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
@@ -482,7 +471,6 @@ export function processUpdateQueue<State>(
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
@@ -508,7 +496,6 @@ export function processUpdateQueue<State>(
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
@@ -519,14 +506,6 @@ export function processUpdateQueue<State>(
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Mark the event time of this update as relevant to this render pass.
// TODO: This should ideally use the true event time of this update rather than
// its priority which is a derived and not reversible value.
// TODO: We should skip this update if it was already committed but currently
// we have no way of detecting the difference between a committed and suspended
// update here.
markRenderEventTimeAndConfig(updateEventTime, update.suspenseConfig);
// Process this update.
newState = getStateFromUpdate(
workInProgress,
@@ -3318,85 +3318,6 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
});
});
// @gate experimental
it('delays showing loading state until after busyDelayMs + busyMinDurationMs', async () => {
let transition;
function App() {
const [show, setShow] = useState(false);
const [startTransition, isPending] = useTransition({
busyDelayMs: 1000,
busyMinDurationMs: 2000,
});
transition = () => {
startTransition(() => {
setShow(true);
});
};
return (
<Suspense
fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
{show ? (
<AsyncText text={`After... Pending: ${isPending}`} />
) : (
<Text text={`Before... Pending: ${isPending}`} />
)}
</Suspense>
);
}
ReactNoop.render(<App />);
expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: false'),
]);
await act(async () => {
Scheduler.unstable_runWithPriority(
Scheduler.unstable_UserBlockingPriority,
transition,
);
expect(Scheduler).toFlushAndYield([
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
]);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
// Resolve the promise. The whole tree has now completed. However,
// because we exceeded the busy threshold, we won't commit the
// result yet.
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
await resolveText('After... Pending: false');
expect(Scheduler).toHaveYielded([
'Promise resolved [After... Pending: false]',
]);
expect(Scheduler).toFlushAndYield(['After... Pending: false']);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Advance time until just before the `busyMinDuration` threshold.
Scheduler.unstable_advanceTime(999);
await advanceTimers(999);
expect(ReactNoop.getChildren()).toEqual([
span('Before... Pending: true'),
]);
// Advance time just a bit more. Now we complete the transition.
Scheduler.unstable_advanceTime(300);
await advanceTimers(300);
expect(ReactNoop.getChildren()).toEqual([
span('After... Pending: false'),
]);
});
});
});
describe('useDeferredValue', () => {
@@ -2860,74 +2860,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
});
// @gate experimental
it('supports delaying a busy spinner from disappearing', async () => {
const SUSPENSE_CONFIG = {
timeoutMs: 10000,
busyDelayMs: 500,
busyMinDurationMs: 400,
};
let transitionToPage;
function App() {
const [page, setPage] = React.useState('A');
const [startLoading, isLoading] = React.unstable_useTransition(
SUSPENSE_CONFIG,
);
transitionToPage = nextPage => startLoading(() => setPage(nextPage));
return (
<Fragment>
<Text text={page} />
{isLoading ? <Text text="L" /> : null}
</Fragment>
);
}
// Initial render.
ReactNoop.render(<App />);
expect(Scheduler).toFlushAndYield(['A']);
expect(ReactNoop.getChildren()).toEqual([span('A')]);
await ReactNoop.act(async () => {
transitionToPage('B');
// Rendering B is quick and we didn't have enough
// time to show the loading indicator.
Scheduler.unstable_advanceTime(200);
await advanceTimers(200);
expect(Scheduler).toFlushAndYield(['A', 'L', 'B']);
expect(ReactNoop.getChildren()).toEqual([span('B')]);
});
await ReactNoop.act(async () => {
transitionToPage('C');
// Rendering C is a bit slower so we've already showed
// the loading indicator.
Scheduler.unstable_advanceTime(600);
await advanceTimers(600);
expect(Scheduler).toFlushAndYield(['B', 'L', 'C']);
// We're technically done now but we haven't shown the
// loading indicator for long enough yet so we'll suspend
// while we keep it on the screen a bit longer.
expect(ReactNoop.getChildren()).toEqual([span('B'), span('L')]);
Scheduler.unstable_advanceTime(400);
await advanceTimers(400);
expect(ReactNoop.getChildren()).toEqual([span('C')]);
});
await ReactNoop.act(async () => {
transitionToPage('D');
// Rendering D is very slow so we've already showed
// the loading indicator.
Scheduler.unstable_advanceTime(1000);
await advanceTimers(1000);
expect(Scheduler).toFlushAndYield(['C', 'L', 'D']);
// However, since we exceeded the minimum time to show
// the loading indicator, we commit immediately.
expect(ReactNoop.getChildren()).toEqual([span('D')]);
});
});
it("suspended commit remains suspended even if there's another update at same expiration", async () => {
// Regression test
function App({text}) {