Skip to content

Support nesting of React.startTransition and ReactDOM.flushSync #21136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions packages/react-reconciler/src/ReactEventPriorities.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
* @flow
*/

import type {Lane, Lanes} from './ReactFiberLane.new';
import type {Lanes} from './ReactFiberLane.new';

import ReactSharedInternals from 'shared/ReactSharedInternals';

import {
NoLane,
SyncLane,
InputContinuousLane,
DefaultLane,
Expand All @@ -19,30 +20,41 @@ import {
includesNonIdleWork,
} from './ReactFiberLane.new';

export opaque type EventPriority = Lane;
const {ReactCurrentBatchConfig} = ReactSharedInternals;

export opaque type EventPriority = number;

export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const IdleEventPriority: EventPriority = IdleLane;

let currentUpdatePriority: EventPriority = NoLane;
// This should stay in sync with the isomorphic package (ReactStartTransition).
// Intentionally not using a shared module, because this crosses a package
// boundary: importing from a shared module would give a false sense of
// DRYness, because it's theoretically possible for for the renderer and
// the isomorphic package to be out of sync. We don't fully support that, but we
// should try (within reason) to be resilient.
//
// The value is an arbitrary transition lane. I picked a lane in the middle of
// the bitmask because it's unlikely to change meaning.
export const TransitionEventPriority = 0b0000000000000001000000000000000;

export function getCurrentUpdatePriority(): EventPriority {
return currentUpdatePriority;
return ReactCurrentBatchConfig.transition;
}

export function setCurrentUpdatePriority(newPriority: EventPriority) {
currentUpdatePriority = newPriority;
ReactCurrentBatchConfig.transition = newPriority;
}

export function runWithPriority<T>(priority: EventPriority, fn: () => T): T {
const previousPriority = currentUpdatePriority;
const previousPriority = ReactCurrentBatchConfig.transition;
try {
currentUpdatePriority = priority;
ReactCurrentBatchConfig.transition = priority;
return fn();
} finally {
currentUpdatePriority = previousPriority;
ReactCurrentBatchConfig.transition = previousPriority;
}
}

Expand Down
30 changes: 21 additions & 9 deletions packages/react-reconciler/src/ReactEventPriorities.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
* @flow
*/

import type {Lane, Lanes} from './ReactFiberLane.old';
import type {Lanes} from './ReactFiberLane.old';

import ReactSharedInternals from 'shared/ReactSharedInternals';

import {
NoLane,
SyncLane,
InputContinuousLane,
DefaultLane,
Expand All @@ -19,30 +20,41 @@ import {
includesNonIdleWork,
} from './ReactFiberLane.old';

export opaque type EventPriority = Lane;
const {ReactCurrentBatchConfig} = ReactSharedInternals;

export opaque type EventPriority = number;

export const DiscreteEventPriority: EventPriority = SyncLane;
export const ContinuousEventPriority: EventPriority = InputContinuousLane;
export const DefaultEventPriority: EventPriority = DefaultLane;
export const IdleEventPriority: EventPriority = IdleLane;

let currentUpdatePriority: EventPriority = NoLane;
// This should stay in sync with the isomorphic package (ReactStartTransition).
// Intentionally not using a shared module, because this crosses a package
// boundary: importing from a shared module would give a false sense of
// DRYness, because it's theoretically possible for for the renderer and
// the isomorphic package to be out of sync. We don't fully support that, but we
// should try (within reason) to be resilient.
//
// The value is an arbitrary transition lane. I picked a lane in the middle of
// the bitmask because it's unlikely to change meaning.
export const TransitionEventPriority = 0b0000000000000001000000000000000;

export function getCurrentUpdatePriority(): EventPriority {
return currentUpdatePriority;
return ReactCurrentBatchConfig.transition;
}

export function setCurrentUpdatePriority(newPriority: EventPriority) {
currentUpdatePriority = newPriority;
ReactCurrentBatchConfig.transition = newPriority;
}

export function runWithPriority<T>(priority: EventPriority, fn: () => T): T {
const previousPriority = currentUpdatePriority;
const previousPriority = ReactCurrentBatchConfig.transition;
try {
currentUpdatePriority = priority;
ReactCurrentBatchConfig.transition = priority;
return fn();
} finally {
currentUpdatePriority = previousPriority;
ReactCurrentBatchConfig.transition = previousPriority;
}
}

Expand Down
12 changes: 8 additions & 4 deletions packages/react-reconciler/src/ReactFiberHooks.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
} from './ReactFiberLane.new';
import {
ContinuousEventPriority,
TransitionEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
higherEventPriority,
Expand Down Expand Up @@ -1664,8 +1665,9 @@ function updateMemo<T>(
function mountDeferredValue<T>(value: T): T {
const [prevValue, setValue] = mountState(value);
mountEffect(() => {
// TODO: Replace with setCurrentUpdatePriority
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
ReactCurrentBatchConfig.transition = TransitionEventPriority;
try {
setValue(value);
} finally {
Expand All @@ -1678,8 +1680,9 @@ function mountDeferredValue<T>(value: T): T {
function updateDeferredValue<T>(value: T): T {
const [prevValue, setValue] = updateState(value);
updateEffect(() => {
// TODO: Replace with setCurrentUpdatePriority
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
ReactCurrentBatchConfig.transition = TransitionEventPriority;
try {
setValue(value);
} finally {
Expand All @@ -1693,7 +1696,7 @@ function rerenderDeferredValue<T>(value: T): T {
const [prevValue, setValue] = rerenderState(value);
updateEffect(() => {
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
ReactCurrentBatchConfig.transition = TransitionEventPriority;
try {
setValue(value);
} finally {
Expand All @@ -1711,8 +1714,9 @@ function startTransition(setPending, callback) {

setPending(true);

// TODO: Replace with setCurrentUpdatePriority
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
ReactCurrentBatchConfig.transition = TransitionEventPriority;
try {
setPending(false);
callback();
Expand Down
12 changes: 8 additions & 4 deletions packages/react-reconciler/src/ReactFiberHooks.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
} from './ReactFiberLane.old';
import {
ContinuousEventPriority,
TransitionEventPriority,
getCurrentUpdatePriority,
setCurrentUpdatePriority,
higherEventPriority,
Expand Down Expand Up @@ -1664,8 +1665,9 @@ function updateMemo<T>(
function mountDeferredValue<T>(value: T): T {
const [prevValue, setValue] = mountState(value);
mountEffect(() => {
// TODO: Replace with setCurrentUpdatePriority
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
ReactCurrentBatchConfig.transition = TransitionEventPriority;
try {
setValue(value);
} finally {
Expand All @@ -1678,8 +1680,9 @@ function mountDeferredValue<T>(value: T): T {
function updateDeferredValue<T>(value: T): T {
const [prevValue, setValue] = updateState(value);
updateEffect(() => {
// TODO: Replace with setCurrentUpdatePriority
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
ReactCurrentBatchConfig.transition = TransitionEventPriority;
try {
setValue(value);
} finally {
Expand All @@ -1693,7 +1696,7 @@ function rerenderDeferredValue<T>(value: T): T {
const [prevValue, setValue] = rerenderState(value);
updateEffect(() => {
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
ReactCurrentBatchConfig.transition = TransitionEventPriority;
try {
setValue(value);
} finally {
Expand All @@ -1711,8 +1714,9 @@ function startTransition(setPending, callback) {

setPending(true);

// TODO: Replace with setCurrentUpdatePriority
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = 1;
ReactCurrentBatchConfig.transition = TransitionEventPriority;
try {
setPending(false);
callback();
Expand Down
34 changes: 18 additions & 16 deletions packages/react-reconciler/src/ReactFiberWorkLoop.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ import {
includesNonIdleWork,
includesOnlyRetries,
includesOnlyTransitions,
isTransitionLane,
getNextLanes,
markStarvedLanesAsExpired,
getLanesToRetrySynchronouslyOnError,
Expand All @@ -170,7 +171,6 @@ import {
higherEventPriority,
lanesToEventPriority,
} from './ReactEventPriorities.new';
import {requestCurrentTransition, NoTransition} from './ReactFiberTransition';
import {beginWork as originalBeginWork} from './ReactFiberBeginWork.new';
import {completeWork} from './ReactFiberCompleteWork.new';
import {unwindWork, unwindInterruptedWork} from './ReactFiberUnwindWork.new';
Expand Down Expand Up @@ -401,8 +401,14 @@ export function requestUpdateLane(fiber: Fiber): Lane {
return pickArbitraryLane(workInProgressRootRenderLanes);
}

const isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
// The opaque type returned by the host config is internally a lane.
// TODO: Move this type conversion to the event priority module.
const eventPriority: Lane = (getCurrentUpdatePriority(): any);

// Check if this is a transition. These are special because unlike other
// priorities, we don't assign the same lane to all transitions. We assign
// one of multiple possible lanes, so that transitions can run in parallel.
if (isTransitionLane(eventPriority)) {
// The algorithm for assigning an update to a lane should be stable for all
// updates at the same priority within the same event. To do this, the
// inputs to the algorithm must be the same.
Expand All @@ -417,25 +423,21 @@ export function requestUpdateLane(fiber: Fiber): Lane {
return currentEventTransitionLane;
}

// Updates originating inside certain React methods, like flushSync, have
// their priority set by tracking it with a context variable.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
return updateLane;
if (eventPriority !== NoLane) {
// If this isn't a transition, and an event priority is set, we can use the
// event priority as a lane directly. (Again, the EventPriority type is
// opaque to avoid leaking the Lane type, but *pssst* it's really a Lane.)
return eventPriority;
}

// This update originated outside React. Ask the host environement for an
// appropriate priority, based on the type of event.
// This update originated outside React, so no priority was set. Ask the host
// environement for an appropriate priority, based on the type of event.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const eventLane: Lane = (getCurrentEventPriority(): any);
return eventLane;
const hostEventPriority: Lane = (getCurrentEventPriority(): any);
return hostEventPriority;
}

function requestRetryLane(fiber: Fiber) {
Expand Down
34 changes: 18 additions & 16 deletions packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ import {
includesNonIdleWork,
includesOnlyRetries,
includesOnlyTransitions,
isTransitionLane,
getNextLanes,
markStarvedLanesAsExpired,
getLanesToRetrySynchronouslyOnError,
Expand All @@ -170,7 +171,6 @@ import {
higherEventPriority,
lanesToEventPriority,
} from './ReactEventPriorities.old';
import {requestCurrentTransition, NoTransition} from './ReactFiberTransition';
import {beginWork as originalBeginWork} from './ReactFiberBeginWork.old';
import {completeWork} from './ReactFiberCompleteWork.old';
import {unwindWork, unwindInterruptedWork} from './ReactFiberUnwindWork.old';
Expand Down Expand Up @@ -401,8 +401,14 @@ export function requestUpdateLane(fiber: Fiber): Lane {
return pickArbitraryLane(workInProgressRootRenderLanes);
}

const isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
// The opaque type returned by the host config is internally a lane.
// TODO: Move this type conversion to the event priority module.
const eventPriority: Lane = (getCurrentUpdatePriority(): any);

// Check if this is a transition. These are special because unlike other
// priorities, we don't assign the same lane to all transitions. We assign
// one of multiple possible lanes, so that transitions can run in parallel.
if (isTransitionLane(eventPriority)) {
// The algorithm for assigning an update to a lane should be stable for all
// updates at the same priority within the same event. To do this, the
// inputs to the algorithm must be the same.
Expand All @@ -417,25 +423,21 @@ export function requestUpdateLane(fiber: Fiber): Lane {
return currentEventTransitionLane;
}

// Updates originating inside certain React methods, like flushSync, have
// their priority set by tracking it with a context variable.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const updateLane: Lane = (getCurrentUpdatePriority(): any);
if (updateLane !== NoLane) {
return updateLane;
if (eventPriority !== NoLane) {
// If this isn't a transition, and an event priority is set, we can use the
// event priority as a lane directly. (Again, the EventPriority type is
// opaque to avoid leaking the Lane type, but *pssst* it's really a Lane.)
return eventPriority;
}

// This update originated outside React. Ask the host environement for an
// appropriate priority, based on the type of event.
// This update originated outside React, so no priority was set. Ask the host
// environement for an appropriate priority, based on the type of event.
//
// The opaque type returned by the host config is internally a lane, so we can
// use that directly.
// TODO: Move this type conversion to the event priority module.
const eventLane: Lane = (getCurrentEventPriority(): any);
return eventLane;
const hostEventPriority: Lane = (getCurrentEventPriority(): any);
return hostEventPriority;
}

function requestRetryLane(fiber: Fiber) {
Expand Down
Loading