Skip to content
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

Warn if addTransitionType is called when there are no pending Actions #32793

Merged
merged 1 commit into from
Apr 1, 2025
Merged
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
16 changes: 16 additions & 0 deletions packages/react-reconciler/src/ReactFiberHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2219,6 +2219,11 @@ function handleActionReturnValue<S, P>(
typeof returnValue.then === 'function'
) {
const thenable = ((returnValue: any): Thenable<Awaited<S>>);
if (__DEV__) {
// Keep track of the number of async transitions still running so we can warn.
ReactSharedInternals.asyncTransitions++;
thenable.then(releaseAsyncTransition, releaseAsyncTransition);
}
// Attach a listener to read the return state of the action. As soon as
// this resolves, we can run the next action in the sequence.
thenable.then(
Expand Down Expand Up @@ -3026,6 +3031,12 @@ function updateDeferredValueImpl<T>(
}
}

function releaseAsyncTransition() {
if (__DEV__) {
ReactSharedInternals.asyncTransitions--;
}
}

function startTransition<S>(
fiber: Fiber,
queue: UpdateQueue<S | Thenable<S>, BasicStateAction<S | Thenable<S>>>,
Expand Down Expand Up @@ -3083,6 +3094,11 @@ function startTransition<S>(
typeof returnValue.then === 'function'
) {
const thenable = ((returnValue: any): Thenable<mixed>);
if (__DEV__) {
// Keep track of the number of async transitions still running so we can warn.
ReactSharedInternals.asyncTransitions++;
thenable.then(releaseAsyncTransition, releaseAsyncTransition);
}
// Create a thenable that resolves to `finishedState` once the async
// action has completed.
const thenableForFinishedState = chainThenableValue(
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/ReactSharedInternalsClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export type SharedStateClient = {
// ReactCurrentActQueue
actQueue: null | Array<RendererTask>,

// When zero this means we're outside an async startTransition.
asyncTransitions: number,

// Used to reproduce behavior of `batchedUpdates` in legacy mode.
isBatchingLegacy: boolean,
didScheduleLegacyUpdate: boolean,
Expand Down Expand Up @@ -75,6 +78,7 @@ if (enableViewTransition) {

if (__DEV__) {
ReactSharedInternals.actQueue = null;
ReactSharedInternals.asyncTransitions = 0;
ReactSharedInternals.isBatchingLegacy = false;
ReactSharedInternals.didScheduleLegacyUpdate = false;
ReactSharedInternals.didUsePromise = false;
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/ReactStartTransition.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export type Transition = {
...
};

function releaseAsyncTransition() {
if (__DEV__) {
ReactSharedInternals.asyncTransitions--;
}
}

export function startTransition(
scope: () => void,
options?: StartTransitionOptions,
Expand Down Expand Up @@ -67,6 +73,11 @@ export function startTransition(
returnValue !== null &&
typeof returnValue.then === 'function'
) {
if (__DEV__) {
// Keep track of the number of async transitions still running so we can warn.
ReactSharedInternals.asyncTransitions++;
returnValue.then(releaseAsyncTransition, releaseAsyncTransition);
}
returnValue.then(noop, reportGlobalError);
}
} catch (error) {
Expand Down
19 changes: 19 additions & 0 deletions packages/react/src/ReactTransitionType.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@ export function addTransitionType(type: string): void {
pendingTransitionTypes = pendingGestureTransitionTypes = [];
}
} else {
if (__DEV__) {
if (
ReactSharedInternals.T === null &&
ReactSharedInternals.asyncTransitions === 0
) {
if (enableGestureTransition) {
console.error(
'addTransitionType can only be called inside a `startTransition()` ' +
'or `startGestureTransition()` callback. ' +
'It must be associated with a specific Transition.',
);
} else {
console.error(
'addTransitionType can only be called inside a `startTransition()` ' +
'callback. It must be associated with a specific Transition.',
);
}
}
}
// Otherwise we're either inside a synchronous startTransition
// or in the async gap of one, which we track globally.
pendingTransitionTypes = ReactSharedInternals.V;
Expand Down
Loading