From 792afc225f8a160a67aba9ad3742ca8aac0c1b9c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 23 Jun 2026 17:08:08 +0000 Subject: [PATCH] docs: update startTransition async guidance for React 19 Clarify that useTransition supports async Actions in React 19, including state updates after await. Reserve the post-await wrapping requirement for standalone startTransition. Rephrase troubleshooting to avoid implying async callbacks are disallowed. Fixes reactjs/react.dev#7172 --- .../reference/react/startTransition.md | 4 +-- src/content/reference/react/useActionState.md | 2 +- src/content/reference/react/useTransition.md | 28 +++++++++++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/content/reference/react/startTransition.md b/src/content/reference/react/startTransition.md index fba28f6d1bc..e5b9fdd8d12 100644 --- a/src/content/reference/react/startTransition.md +++ b/src/content/reference/react/startTransition.md @@ -41,7 +41,7 @@ function TabContainer() { #### Parameters {/*parameters*/} -* `action`: A function that updates some state by calling one or more [`set` functions](/reference/react/useState#setstate). React calls `action` immediately with no parameters and marks all state updates scheduled synchronously during the `action` function call as Transitions. Any async calls awaited in the `action` will be included in the transition, but currently require wrapping any `set` functions after the `await` in an additional `startTransition` (see [Troubleshooting](/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition)). State updates marked as Transitions will be [non-blocking](#marking-a-state-update-as-a-non-blocking-transition) and [will not display unwanted loading indicators.](/reference/react/useTransition#preventing-unwanted-loading-indicators). +* `action`: A function that updates some state by calling one or more [`set` functions](/reference/react/useState#setstate). The function can be synchronous or asynchronous. React calls `action` immediately with no parameters and marks all state updates scheduled during the `action` as Transitions. State updates marked as Transitions will be [non-blocking](#marking-a-state-update-as-a-non-blocking-transition) and [will not display unwanted loading indicators.](/reference/react/useTransition#preventing-unwanted-loading-indicators). If you need to track a pending Transition, use [`useTransition`](/reference/react/useTransition) instead. #### Returns {/*returns*/} @@ -55,7 +55,7 @@ function TabContainer() { * The function you pass to `startTransition` is called immediately, marking all state updates that happen while it executes as Transitions. If you try to perform state updates in a `setTimeout`, for example, they won't be marked as Transitions. -* You must wrap any state updates after any async requests in another `startTransition` to mark them as Transitions. This is a known limitation that we will fix in the future (see [Troubleshooting](/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition)). +* When using the standalone `startTransition` function, state updates after `await` are not automatically marked as Transitions. Wrap them in another `startTransition` call (see [Troubleshooting](/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition)). If you use [`useTransition`](/reference/react/useTransition) in React 19, the `startTransition` function it returns supports async Actions, and state updates after `await` are included in the Transition automatically. * A state update marked as a Transition will be interrupted by other state updates. For example, if you update a chart component inside a Transition, but then start typing into an input while the chart is in the middle of a re-render, React will restart the rendering work on the chart component after handling the input state update. diff --git a/src/content/reference/react/useActionState.md b/src/content/reference/react/useActionState.md index 581eebc634b..9bdfc6686e5 100644 --- a/src/content/reference/react/useActionState.md +++ b/src/content/reference/react/useActionState.md @@ -103,7 +103,7 @@ Each time you call `dispatchAction`, React calls the `reducerAction` with the `a * `reducerAction` can be sync or async. It can perform sync actions like showing a notification, or async actions like posting updates to a server. * `reducerAction` is not invoked twice in `` since `reducerAction` is designed to allow side effects. * The return type of `reducerAction` must match the type of `initialState`. If TypeScript infers a mismatch, you may need to explicitly annotate your state type. -* If you set state after `await` in the `reducerAction` you currently need to wrap the state update in an additional `startTransition`. See the [startTransition](/reference/react/useTransition#react-doesnt-treat-my-state-update-after-await-as-a-transition) docs for more info. +* If you call `reducerAction` outside of a Transition (for example, directly in an event handler), wrap it in [`startTransition`](/reference/react/startTransition). See [Troubleshooting](/reference/react/useActionState#async-function-outside-transition) for more info. * When using Server Functions, `actionPayload` needs to be [serializable](/reference/rsc/use-server#serializable-parameters-and-return-values) (values like plain objects, arrays, strings, and numbers). diff --git a/src/content/reference/react/useTransition.md b/src/content/reference/react/useTransition.md index 426df1f7b26..2135b6a5e13 100644 --- a/src/content/reference/react/useTransition.md +++ b/src/content/reference/react/useTransition.md @@ -95,7 +95,7 @@ function SubmitButton({ submitAction }) { #### Parameters {/*starttransition-parameters*/} -* `action`: A function that updates some state by calling one or more [`set` functions](/reference/react/useState#setstate). React calls `action` immediately with no parameters and marks all state updates scheduled synchronously during the `action` function call as Transitions. Any async calls that are awaited in the `action` will be included in the Transition, but currently require wrapping any `set` functions after the `await` in an additional `startTransition` (see [Troubleshooting](#react-doesnt-treat-my-state-update-after-await-as-a-transition)). State updates marked as Transitions will be [non-blocking](#perform-non-blocking-updates-with-actions) and [will not display unwanted loading indicators](#preventing-unwanted-loading-indicators). +* `action`: A function that updates some state by calling one or more [`set` functions](/reference/react/useState#setstate). The function can be synchronous or asynchronous. React calls `action` immediately with no parameters and marks all state updates scheduled during the `action` as Transitions, including updates after `await`. State updates marked as Transitions will be [non-blocking](#perform-non-blocking-updates-with-actions) and [will not display unwanted loading indicators](#preventing-unwanted-loading-indicators). #### Returns {/*starttransition-returns*/} @@ -109,8 +109,6 @@ function SubmitButton({ submitAction }) { * The function you pass to `startTransition` is called immediately, marking all state updates that happen while it executes as Transitions. If you try to perform state updates in a `setTimeout`, for example, they won't be marked as Transitions. -* You must wrap any state updates after any async requests in another `startTransition` to mark them as Transitions. This is a known limitation that we will fix in the future (see [Troubleshooting](#react-doesnt-treat-my-state-update-after-await-as-a-transition)). - * The `startTransition` function has a stable identity, so you will often see it omitted from Effect dependencies, but including it will not cause the Effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. [Learn more about removing Effect dependencies.](/learn/removing-effect-dependencies#move-dynamic-objects-and-functions-inside-your-effect) * A state update marked as a Transition will be interrupted by other state updates. For example, if you update a chart component inside a Transition, but then start typing into an input while the chart is in the middle of a re-render, React will restart the rendering work on the chart component after handling the input update. @@ -1676,7 +1674,7 @@ startTransition(() => { }); ``` -The function you pass to `startTransition` must be synchronous. You can't mark an update as a Transition like this: +The function you pass to `startTransition` is called immediately, but updates must be scheduled *during* that call. You can't mark an update as a Transition if you defer it with `setTimeout`: ```js startTransition(() => { @@ -1702,17 +1700,31 @@ setTimeout(() => { ### React doesn't treat my state update after `await` as a Transition {/*react-doesnt-treat-my-state-update-after-await-as-a-transition*/} -When you use `await` inside a `startTransition` function, the state updates that happen after the `await` are not marked as Transitions. You must wrap state updates after each `await` in a `startTransition` call: +In React 19, the `startTransition` function returned by `useTransition` supports async Actions. State updates after `await` are automatically included in the Transition: + +```js +const [isPending, startTransition] = useTransition(); + +startTransition(async () => { + await someAsyncFunction(); + // ✅ Updates after await are part of the Transition + setPage('/about'); +}); +``` + +This limitation only applies to the standalone [`startTransition`](/reference/react/startTransition) function. When you use it outside a component, state updates after `await` are not automatically marked as Transitions. Wrap them in another `startTransition` call: ```js +import { startTransition } from 'react'; + startTransition(async () => { await someAsyncFunction(); - // ❌ Not using startTransition after await + // ❌ Not marked as a Transition with standalone startTransition setPage('/about'); }); ``` -However, this works instead: +This works instead: ```js startTransition(async () => { @@ -1724,8 +1736,6 @@ startTransition(async () => { }); ``` -This is a JavaScript limitation due to React losing the scope of the async context. In the future, when [AsyncContext](https://github.com/tc39/proposal-async-context) is available, this limitation will be removed. - --- ### I want to call `useTransition` from outside a component {/*i-want-to-call-usetransition-from-outside-a-component*/}