Skip to content

Commit a3e9565

Browse files
committed
Suspend the commit even if it is complete if there is a minimum delay
This can be used to implement spinners that don't flicker if the data and rendering is really fast.
1 parent 8736c23 commit a3e9565

File tree

1 file changed

+54
-0
lines changed

1 file changed

+54
-0
lines changed

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,27 @@ function renderRoot(
946946
}
947947
case RootCompleted: {
948948
// The work completed. Ready to commit.
949+
if (
950+
!isSync &&
951+
workInProgressRootLatestProcessedExpirationTime !== Sync &&
952+
workInProgressRootCanSuspendUsingConfig !== null
953+
) {
954+
// If we have exceeded the minimum loading delay, which probably
955+
// means we have shown a spinner already, we might have to suspend
956+
// a bit longer to ensure that the spinner is shown for enough time.
957+
const msUntilTimeout = computeMsUntilSuspenseLoadingDelay(
958+
workInProgressRootLatestProcessedExpirationTime,
959+
expirationTime,
960+
workInProgressRootCanSuspendUsingConfig,
961+
);
962+
if (msUntilTimeout > 10) {
963+
root.timeoutHandle = scheduleTimeout(
964+
commitRoot.bind(null, root, expirationTime),
965+
msUntilTimeout,
966+
);
967+
return null;
968+
}
969+
}
949970
return commitRoot.bind(null, root, expirationTime);
950971
}
951972
default: {
@@ -1909,6 +1930,39 @@ function jnd(timeElapsed: number) {
19091930
: ceil(timeElapsed / 1960) * 1960;
19101931
}
19111932

1933+
function computeMsUntilSuspenseLoadingDelay(
1934+
mostRecentEventTime: ExpirationTime,
1935+
committedExpirationTime: ExpirationTime,
1936+
suspenseConfig: SuspenseConfig,
1937+
) {
1938+
if (disableYielding) {
1939+
// Timeout immediately when yielding is disabled.
1940+
return 0;
1941+
}
1942+
1943+
const minLoadingDurationMs = (suspenseConfig.minLoadingDurationMs: any) | 0;
1944+
if (minLoadingDurationMs <= 0) {
1945+
return 0;
1946+
}
1947+
const loadingDelayMs = (suspenseConfig.loadingDelayMs: any) | 0;
1948+
1949+
// Compute the time until this render pass would expire.
1950+
const currentTimeMs: number = now();
1951+
const eventTimeMs: number = inferTimeFromExpirationTime(
1952+
mostRecentEventTime,
1953+
suspenseConfig,
1954+
);
1955+
const timeElapsed = currentTimeMs - eventTimeMs;
1956+
if (timeElapsed <= loadingDelayMs) {
1957+
// If we haven't yet waited longer than the initial delay, we don't
1958+
// have to wait any additional time.
1959+
return 0;
1960+
}
1961+
const msUntilTimeout = timeElapsed - loadingDelayMs - minLoadingDurationMs;
1962+
// This is the value that is passed to `setTimeout`.
1963+
return msUntilTimeout;
1964+
}
1965+
19121966
function computeMsUntilTimeout(
19131967
mostRecentEventTime: ExpirationTime,
19141968
committedExpirationTime: ExpirationTime,

0 commit comments

Comments
 (0)