diff --git a/packages/react-cache/src/__tests__/ReactCache-test.internal.js b/packages/react-cache/src/__tests__/ReactCache-test.internal.js
index e87d53e063c40..36a6f0f84744b 100644
--- a/packages/react-cache/src/__tests__/ReactCache-test.internal.js
+++ b/packages/react-cache/src/__tests__/ReactCache-test.internal.js
@@ -24,6 +24,8 @@ describe('ReactCache', () => {
beforeEach(() => {
jest.resetModules();
+ let currentPriorityLevel = 3;
+
jest.mock('scheduler', () => {
let callbacks = [];
return {
@@ -38,6 +40,26 @@ describe('ReactCache', () => {
callback();
}
},
+
+ unstable_ImmediatePriority: 1,
+ unstable_UserBlockingPriority: 2,
+ unstable_NormalPriority: 3,
+ unstable_LowPriority: 4,
+ unstable_IdlePriority: 5,
+
+ unstable_runWithPriority(priorityLevel, fn) {
+ const prevPriorityLevel = currentPriorityLevel;
+ currentPriorityLevel = priorityLevel;
+ try {
+ return fn();
+ } finally {
+ currentPriorityLevel = prevPriorityLevel;
+ }
+ },
+
+ unstable_getCurrentPriorityLevel() {
+ return currentPriorityLevel;
+ },
};
});
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js
index 5712dbc9cd6d1..baa100f0d266a 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.js
@@ -15,8 +15,18 @@ import type {Interaction} from 'scheduler/src/Tracing';
import {
__interactionsRef,
__subscriberRef,
- unstable_wrap as Schedule_tracing_wrap,
+ unstable_wrap as Scheduler_tracing_wrap,
} from 'scheduler/tracing';
+import {
+ unstable_next as Scheduler_next,
+ unstable_getCurrentPriorityLevel as getCurrentPriorityLevel,
+ unstable_runWithPriority as runWithPriority,
+ unstable_ImmediatePriority as ImmediatePriority,
+ unstable_UserBlockingPriority as UserBlockingPriority,
+ unstable_NormalPriority as NormalPriority,
+ unstable_LowPriority as LowPriority,
+ unstable_IdlePriority as IdlePriority,
+} from 'scheduler';
import {
invokeGuardedCallback,
hasCaughtError,
@@ -122,7 +132,7 @@ import {
computeAsyncExpiration,
computeInteractiveExpiration,
} from './ReactFiberExpirationTime';
-import {ConcurrentMode, ProfileMode} from './ReactTypeOfMode';
+import {ConcurrentMode, ProfileMode, NoContext} from './ReactTypeOfMode';
import {enqueueUpdate, resetCurrentlyProcessingQueue} from './ReactUpdateQueue';
import {createCapturedValue} from './ReactCapturedValue';
import {
@@ -242,11 +252,6 @@ if (__DEV__) {
// Used to ensure computeUniqueAsyncExpiration is monotonically decreasing.
let lastUniqueAsyncExpiration: number = Sync - 1;
-// Represents the expiration time that incoming updates should use. (If this
-// is NoWork, use the default strategy: async updates in async mode, sync
-// updates in sync mode.)
-let expirationContext: ExpirationTime = NoWork;
-
let isWorking: boolean = false;
// The next work in progress fiber that we're currently working on.
@@ -793,9 +798,11 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
// TODO: Avoid this extra callback by mutating the tracing ref directly,
// like we do at the beginning of commitRoot. I've opted not to do that
// here because that code is still in flux.
- callback = Schedule_tracing_wrap(callback);
+ callback = Scheduler_tracing_wrap(callback);
}
- passiveEffectCallbackHandle = schedulePassiveEffects(callback);
+ passiveEffectCallbackHandle = runWithPriority(NormalPriority, () => {
+ return schedulePassiveEffects(callback);
+ });
passiveEffectCallback = callback;
}
@@ -1579,52 +1586,58 @@ function computeUniqueAsyncExpiration(): ExpirationTime {
}
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
+ const priorityLevel = getCurrentPriorityLevel();
+
let expirationTime;
- if (expirationContext !== NoWork) {
- // An explicit expiration context was set;
- expirationTime = expirationContext;
- } else if (isWorking) {
- if (isCommitting) {
- // Updates that occur during the commit phase should have sync priority
- // by default.
- expirationTime = Sync;
- } else {
- // Updates during the render phase should expire at the same time as
- // the work that is being rendered.
- expirationTime = nextRenderExpirationTime;
- }
+ if ((fiber.mode & ConcurrentMode) === NoContext) {
+ // Outside of concurrent mode, updates are always synchronous.
+ expirationTime = Sync;
+ } else if (isWorking && !isCommitting) {
+ // During render phase, updates expire during as the current render.
+ expirationTime = nextRenderExpirationTime;
} else {
- // No explicit expiration context was set, and we're not currently
- // performing work. Calculate a new expiration time.
- if (fiber.mode & ConcurrentMode) {
- if (isBatchingInteractiveUpdates) {
- // This is an interactive update
+ switch (priorityLevel) {
+ case ImmediatePriority:
+ expirationTime = Sync;
+ break;
+ case UserBlockingPriority:
expirationTime = computeInteractiveExpiration(currentTime);
- } else {
- // This is an async update
+ break;
+ case NormalPriority:
+ // This is a normal, concurrent update
expirationTime = computeAsyncExpiration(currentTime);
- }
- // If we're in the middle of rendering a tree, do not update at the same
- // expiration time that is already rendering.
- if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
- expirationTime -= 1;
- }
- } else {
- // This is a sync update
- expirationTime = Sync;
+ break;
+ case LowPriority:
+ case IdlePriority:
+ expirationTime = Never;
+ break;
+ default:
+ invariant(
+ false,
+ 'Unknown priority level. This error is likely caused by a bug in ' +
+ 'React. Please file an issue.',
+ );
}
- }
- if (isBatchingInteractiveUpdates) {
- // This is an interactive update. Keep track of the lowest pending
- // interactive expiration time. This allows us to synchronously flush
- // all interactive updates when needed.
- if (
- lowestPriorityPendingInteractiveExpirationTime === NoWork ||
- expirationTime < lowestPriorityPendingInteractiveExpirationTime
- ) {
- lowestPriorityPendingInteractiveExpirationTime = expirationTime;
+
+ // If we're in the middle of rendering a tree, do not update at the same
+ // expiration time that is already rendering.
+ if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
+ expirationTime -= 1;
}
}
+
+ // Keep track of the lowest pending interactive expiration time. This
+ // allows us to synchronously flush all interactive updates
+ // when needed.
+ // TODO: Move this to renderer?
+ if (
+ priorityLevel === UserBlockingPriority &&
+ (lowestPriorityPendingInteractiveExpirationTime === NoWork ||
+ expirationTime < lowestPriorityPendingInteractiveExpirationTime)
+ ) {
+ lowestPriorityPendingInteractiveExpirationTime = expirationTime;
+ }
+
return expirationTime;
}
@@ -1862,20 +1875,6 @@ function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
}
}
-function deferredUpdates(fn: () => A): A {
- const currentTime = requestCurrentTime();
- const previousExpirationContext = expirationContext;
- const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
- expirationContext = computeAsyncExpiration(currentTime);
- isBatchingInteractiveUpdates = false;
- try {
- return fn();
- } finally {
- expirationContext = previousExpirationContext;
- isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
- }
-}
-
function syncUpdates(
fn: (A, B, C0, D) => R,
a: A,
@@ -1883,13 +1882,9 @@ function syncUpdates(
c: C0,
d: D,
): R {
- const previousExpirationContext = expirationContext;
- expirationContext = Sync;
- try {
+ return runWithPriority(ImmediatePriority, () => {
return fn(a, b, c, d);
- } finally {
- expirationContext = previousExpirationContext;
- }
+ });
}
// TODO: Everything below this is written as if it has been lifted to the
@@ -1910,7 +1905,6 @@ let unhandledError: mixed | null = null;
let isBatchingUpdates: boolean = false;
let isUnbatchingUpdates: boolean = false;
-let isBatchingInteractiveUpdates: boolean = false;
let completedBatches: Array | null = null;
@@ -2441,7 +2435,9 @@ function completeRoot(
lastCommittedRootDuringThisBatch = root;
nestedUpdateCount = 0;
}
- commitRoot(root, finishedWork);
+ runWithPriority(ImmediatePriority, () => {
+ commitRoot(root, finishedWork);
+ });
}
function onUncaughtError(error: mixed) {
@@ -2507,9 +2503,6 @@ function flushSync(fn: (a: A) => R, a: A): R {
}
function interactiveUpdates(fn: (A, B) => R, a: A, b: B): R {
- if (isBatchingInteractiveUpdates) {
- return fn(a, b);
- }
// If there are any pending interactive updates, synchronously flush them.
// This needs to happen before we read any handlers, because the effect of
// the previous event may influence which handlers are called during
@@ -2523,14 +2516,13 @@ function interactiveUpdates(fn: (A, B) => R, a: A, b: B): R {
performWork(lowestPriorityPendingInteractiveExpirationTime, false);
lowestPriorityPendingInteractiveExpirationTime = NoWork;
}
- const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
const previousIsBatchingUpdates = isBatchingUpdates;
- isBatchingInteractiveUpdates = true;
isBatchingUpdates = true;
try {
- return fn(a, b);
+ return runWithPriority(UserBlockingPriority, () => {
+ return fn(a, b);
+ });
} finally {
- isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
isBatchingUpdates = previousIsBatchingUpdates;
if (!isBatchingUpdates && !isRendering) {
performSyncWork();
@@ -2580,7 +2572,7 @@ export {
unbatchedUpdates,
flushSync,
flushControlled,
- deferredUpdates,
+ Scheduler_next as deferredUpdates,
syncUpdates,
interactiveUpdates,
flushInteractiveUpdates,
diff --git a/packages/react/src/ReactSharedInternals.js b/packages/react/src/ReactSharedInternals.js
index 9cdce1891bc5c..1fe0c2391bd13 100644
--- a/packages/react/src/ReactSharedInternals.js
+++ b/packages/react/src/ReactSharedInternals.js
@@ -12,6 +12,7 @@ import {
unstable_now,
unstable_scheduleCallback,
unstable_runWithPriority,
+ unstable_next,
unstable_getFirstCallbackNode,
unstable_pauseExecution,
unstable_continueExecution,
@@ -53,6 +54,7 @@ if (__UMD__) {
unstable_now,
unstable_scheduleCallback,
unstable_runWithPriority,
+ unstable_next,
unstable_wrapCallback,
unstable_getFirstCallbackNode,
unstable_pauseExecution,
diff --git a/packages/scheduler/npm/umd/scheduler.development.js b/packages/scheduler/npm/umd/scheduler.development.js
index 41ac8e437bbd6..ac632eb288bff 100644
--- a/packages/scheduler/npm/umd/scheduler.development.js
+++ b/packages/scheduler/npm/umd/scheduler.development.js
@@ -54,6 +54,13 @@
);
}
+ function unstable_next() {
+ return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
+ this,
+ arguments
+ );
+ }
+
function unstable_wrapCallback() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
this,
@@ -95,6 +102,7 @@
unstable_cancelCallback: unstable_cancelCallback,
unstable_shouldYield: unstable_shouldYield,
unstable_runWithPriority: unstable_runWithPriority,
+ unstable_next: unstable_next,
unstable_wrapCallback: unstable_wrapCallback,
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
unstable_continueExecution: unstable_continueExecution,
diff --git a/packages/scheduler/npm/umd/scheduler.production.min.js b/packages/scheduler/npm/umd/scheduler.production.min.js
index cea54f4da3cba..da2aefa9e4bf1 100644
--- a/packages/scheduler/npm/umd/scheduler.production.min.js
+++ b/packages/scheduler/npm/umd/scheduler.production.min.js
@@ -54,6 +54,13 @@
);
}
+ function unstable_next() {
+ return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
+ this,
+ arguments
+ );
+ }
+
function unstable_wrapCallback() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
this,
@@ -89,6 +96,7 @@
unstable_cancelCallback: unstable_cancelCallback,
unstable_shouldYield: unstable_shouldYield,
unstable_runWithPriority: unstable_runWithPriority,
+ unstable_next: unstable_next,
unstable_wrapCallback: unstable_wrapCallback,
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
unstable_continueExecution: unstable_continueExecution,
diff --git a/packages/scheduler/npm/umd/scheduler.profiling.min.js b/packages/scheduler/npm/umd/scheduler.profiling.min.js
index cea54f4da3cba..da2aefa9e4bf1 100644
--- a/packages/scheduler/npm/umd/scheduler.profiling.min.js
+++ b/packages/scheduler/npm/umd/scheduler.profiling.min.js
@@ -54,6 +54,13 @@
);
}
+ function unstable_next() {
+ return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_next.apply(
+ this,
+ arguments
+ );
+ }
+
function unstable_wrapCallback() {
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_wrapCallback.apply(
this,
@@ -89,6 +96,7 @@
unstable_cancelCallback: unstable_cancelCallback,
unstable_shouldYield: unstable_shouldYield,
unstable_runWithPriority: unstable_runWithPriority,
+ unstable_next: unstable_next,
unstable_wrapCallback: unstable_wrapCallback,
unstable_getCurrentPriorityLevel: unstable_getCurrentPriorityLevel,
unstable_continueExecution: unstable_continueExecution,
diff --git a/packages/scheduler/src/Scheduler.js b/packages/scheduler/src/Scheduler.js
index a6e27850dab71..df1e9b3bdada0 100644
--- a/packages/scheduler/src/Scheduler.js
+++ b/packages/scheduler/src/Scheduler.js
@@ -264,6 +264,37 @@ function unstable_runWithPriority(priorityLevel, eventHandler) {
}
}
+function unstable_next(eventHandler) {
+ let priorityLevel;
+ switch (currentPriorityLevel) {
+ case ImmediatePriority:
+ case UserBlockingPriority:
+ case NormalPriority:
+ // Shift down to normal priority
+ priorityLevel = NormalPriority;
+ break;
+ default:
+ // Anything lower than normal priority should remain at the current level.
+ priorityLevel = currentPriorityLevel;
+ break;
+ }
+
+ var previousPriorityLevel = currentPriorityLevel;
+ var previousEventStartTime = currentEventStartTime;
+ currentPriorityLevel = priorityLevel;
+ currentEventStartTime = getCurrentTime();
+
+ try {
+ return eventHandler();
+ } finally {
+ currentPriorityLevel = previousPriorityLevel;
+ currentEventStartTime = previousEventStartTime;
+
+ // Before exiting, flush all the immediate work that was scheduled.
+ flushImmediateWork();
+ }
+}
+
function unstable_wrapCallback(callback) {
var parentPriorityLevel = currentPriorityLevel;
return function() {
@@ -688,6 +719,7 @@ export {
IdlePriority as unstable_IdlePriority,
LowPriority as unstable_LowPriority,
unstable_runWithPriority,
+ unstable_next,
unstable_scheduleCallback,
unstable_cancelCallback,
unstable_wrapCallback,
diff --git a/packages/shared/forks/Scheduler.umd.js b/packages/shared/forks/Scheduler.umd.js
index c33b2d0667416..6c9c918dc8779 100644
--- a/packages/shared/forks/Scheduler.umd.js
+++ b/packages/shared/forks/Scheduler.umd.js
@@ -17,8 +17,16 @@ const {
unstable_scheduleCallback,
unstable_shouldYield,
unstable_getFirstCallbackNode,
+ unstable_runWithPriority,
+ unstable_next,
unstable_continueExecution,
unstable_pauseExecution,
+ unstable_getCurrentPriorityLevel,
+ unstable_ImmediatePriority,
+ unstable_UserBlockingPriority,
+ unstable_NormalPriority,
+ unstable_LowPriority,
+ unstable_IdlePriority,
} = ReactInternals.Scheduler;
export {
@@ -27,6 +35,14 @@ export {
unstable_scheduleCallback,
unstable_shouldYield,
unstable_getFirstCallbackNode,
+ unstable_runWithPriority,
+ unstable_next,
unstable_continueExecution,
unstable_pauseExecution,
+ unstable_getCurrentPriorityLevel,
+ unstable_ImmediatePriority,
+ unstable_UserBlockingPriority,
+ unstable_NormalPriority,
+ unstable_LowPriority,
+ unstable_IdlePriority,
};