Movatterモバイル変換


[0]ホーム

URL:


Is this page useful?

useTransition

useTransition is a React Hook that lets you render a part of the UI in the background.

const[isPending,startTransition] =useTransition()

Reference

useTransition()

CalluseTransition at the top level of your component to mark some state updates as Transitions.

import{useTransition}from'react';

functionTabContainer(){
const[isPending,startTransition] =useTransition();
// ...
}

See more examples below.

Parameters

useTransition does not take any parameters.

Returns

useTransition returns an array with exactly two items:

  1. TheisPending flag that tells you whether there is a pending Transition.
  2. ThestartTransition function that lets you mark updates as a Transition.

startTransition(action)

ThestartTransition function returned byuseTransition lets you mark an update as a Transition.

functionTabContainer(){
const[isPending,startTransition] =useTransition();
const[tab,setTab] =useState('about');

functionselectTab(nextTab){
startTransition(()=>{
setTab(nextTab);
});
}
// ...
}

Note

Functions called instartTransition are called “Actions”.

The function passed tostartTransition is called an “Action”. By convention, any callback called insidestartTransition (such as a callback prop) should be namedaction or include the “Action” suffix:

functionSubmitButton({submitAction}){
const[isPending,startTransition] =useTransition();

return(
<button
disabled={isPending}
onClick={()=>{
startTransition(async()=>{
awaitsubmitAction();
});
}}
>
Submit
</button>
);
}

Parameters

  • action: A function that updates some state by calling one or moreset functions. React callsaction immediately with no parameters and marks all state updates scheduled synchronously during theaction function call as Transitions. Any async calls that are awaited in theaction will be included in the Transition, but currently require wrapping anyset functions after theawait in an additionalstartTransition (seeTroubleshooting). State updates marked as Transitions will benon-blocking andwill not display unwanted loading indicators.

Returns

startTransition does not return anything.

Caveats

  • useTransition is a Hook, so it can only be called inside components or custom Hooks. If you need to start a Transition somewhere else (for example, from a data library), call the standalonestartTransition instead.

  • You can wrap an update into a Transition only if you have access to theset function of that state. If you want to start a Transition in response to some prop or a custom Hook value, tryuseDeferredValue instead.

  • The function you pass tostartTransition is called immediately, marking all state updates that happen while it executes as Transitions. If you try to perform state updates in asetTimeout, for example, they won’t be marked as Transitions.

  • You must wrap any state updates after any async requests in anotherstartTransition to mark them as Transitions. This is a known limitation that we will fix in the future (seeTroubleshooting).

  • ThestartTransition 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.

  • 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.

  • Transition updates can’t be used to control text inputs.

  • If there are multiple ongoing Transitions, React currently batches them together. This is a limitation that may be removed in a future release.

Usage

Perform non-blocking updates with Actions

CalluseTransition at the top of your component to create Actions, and access the pending state:

import{useState,useTransition}from'react';

functionCheckoutForm(){
const[isPending,startTransition] =useTransition();
// ...
}

useTransition returns an array with exactly two items:

  1. TheisPending flag that tells you whether there is a pending Transition.
  2. ThestartTransition function that lets you create an Action.

To start a Transition, pass a function tostartTransition like this:

import{useState,useTransition}from'react';
import{updateQuantity}from'./api';

functionCheckoutForm(){
const[isPending,startTransition] =useTransition();
const[quantity,setQuantity] =useState(1);

functiononSubmit(newQuantity){
startTransition(asyncfunction(){
constsavedQuantity =awaitupdateQuantity(newQuantity);
startTransition(()=>{
setQuantity(savedQuantity);
});
});
}
// ...
}

The function passed tostartTransition is called the “Action”. You can update state and (optionally) perform side effects within an Action, and the work will be done in the background without blocking user interactions on the page. A Transition can include multiple Actions, and while a Transition is in progress, your UI stays responsive. For example, if the user clicks a tab but then changes their mind and clicks another tab, the second click will be immediately handled without waiting for the first update to finish.

To give the user feedback about in-progress Transitions, theisPending state switches totrue at the first call tostartTransition, and staystrue until all Actions complete and the final state is shown to the user. Transitions ensure side effects in Actions to complete in order toprevent unwanted loading indicators, and you can provide immediate feedback while the Transition is in progress withuseOptimistic.

The difference between Actions and regular event handling

Example 1 of 2:
Updating the quantity in an Action

In this example, theupdateQuantity function simulates a request to the server to update the item’s quantity in the cart. This function isartificially slowed down so that it takes at least a second to complete the request.

Update the quantity multiple times quickly. Notice that the pending “Total” state is shown while any requests are in progress, and the “Total” updates only after the final request is complete. Because the update is in an Action, the “quantity” can continue to be updated while the request is in progress.

Fork
import{useState,useTransition}from"react";import{updateQuantity}from"./api";importItemfrom"./Item";importTotalfrom"./Total";exportdefaultfunctionApp({}){const[quantity,setQuantity] =useState(1);const[isPending,startTransition] =useTransition();constupdateQuantityAction =asyncnewQuantity=>{// To access the pending state of a transition,// call startTransition again.startTransition(async()=>{constsavedQuantity =awaitupdateQuantity(newQuantity);startTransition(()=>{setQuantity(savedQuantity);});});};return(<div><h1>Checkout</h1><Itemaction={updateQuantityAction}/><hr/><Totalquantity={quantity}isPending={isPending}/></div>);}

This is a basic example to demonstrate how Actions work, but this example does not handle requests completing out of order. When updating the quantity multiple times, it’s possible for the previous requests to finish after later requests causing the quantity to update out of order. This is a known limitation that we will fix in the future (seeTroubleshooting below).

For common use cases, React provides built-in abstractions such as:

These solutions handle request ordering for you. When using Transitions to build your own custom hooks or libraries that manage async state transitions, you have greater control over the request ordering, but you must handle it yourself.


Exposingaction prop from components

You can expose anaction prop from a component to allow a parent to call an Action.

For example, thisTabButton component wraps itsonClick logic in anaction prop:

exportdefaultfunctionTabButton({action,children,isActive}){
const[isPending,startTransition] =useTransition();
if(isActive){
return<b>{children}</b>
}
return(
<buttononClick={()=>{
startTransition(async()=>{
// await the action that's passed in.
// This allows it to be either sync or async.
awaitaction();
});
}}>
{children}
</button>
);
}

Because the parent component updates its state inside theaction, that state update gets marked as a Transition. This means you can click on “Posts” and then immediately click “Contact” and it does not block user interactions:

Fork
import{useTransition}from'react';exportdefaultfunctionTabButton({action,children,isActive}){const[isPending,startTransition] =useTransition();if(isActive){return<b>{children}</b>}if(isPending){return<bclassName="pending">{children}</b>;}return(<buttononClick={async()=>{startTransition(async()=>{// await the action that's passed in.// This allows it to be either sync or async.awaitaction();});}}>{children}</button>);}

Note

When exposing anaction prop from a component, you shouldawait it inside the transition.

This allows theaction callback to be either synchronous or asynchronous without requiring an additionalstartTransition to wrap theawait in the action.


Displaying a pending visual state

You can use theisPending boolean value returned byuseTransition to indicate to the user that a Transition is in progress. For example, the tab button can have a special “pending” visual state:

functionTabButton({action,children,isActive}){
const[isPending,startTransition] =useTransition();
// ...
if(isPending){
return<bclassName="pending">{children}</b>;
}
// ...

Notice how clicking “Posts” now feels more responsive because the tab button itself updates right away:

Fork
import{useTransition}from'react';exportdefaultfunctionTabButton({action,children,isActive}){const[isPending,startTransition] =useTransition();if(isActive){return<b>{children}</b>}if(isPending){return<bclassName="pending">{children}</b>;}return(<buttononClick={()=>{startTransition(async()=>{awaitaction();});}}>{children}</button>);}

Preventing unwanted loading indicators

In this example, thePostsTab component fetches some data usinguse. When you click the “Posts” tab, thePostsTab componentsuspends, causing the closest loading fallback to appear:

Fork
import{Suspense,useState}from'react';importTabButtonfrom'./TabButton.js';importAboutTabfrom'./AboutTab.js';importPostsTabfrom'./PostsTab.js';importContactTabfrom'./ContactTab.js';exportdefaultfunctionTabContainer(){const[tab,setTab] =useState('about');return(<Suspensefallback={<h1>🌀 Loading...</h1>}><TabButtonisActive={tab ==='about'}action={()=>setTab('about')}>        About</TabButton><TabButtonisActive={tab ==='posts'}action={()=>setTab('posts')}>        Posts</TabButton><TabButtonisActive={tab ==='contact'}action={()=>setTab('contact')}>        Contact</TabButton><hr/>{tab ==='about' &&<AboutTab/>}{tab ==='posts' &&<PostsTab/>}{tab ==='contact' &&<ContactTab/>}</Suspense>);}

Hiding the entire tab container to show a loading indicator leads to a jarring user experience. If you adduseTransition toTabButton, you can instead display the pending state in the tab button instead.

Notice that clicking “Posts” no longer replaces the entire tab container with a spinner:

Fork
import{useTransition}from'react';exportdefaultfunctionTabButton({action,children,isActive}){const[isPending,startTransition] =useTransition();if(isActive){return<b>{children}</b>}if(isPending){return<bclassName="pending">{children}</b>;}return(<buttononClick={()=>{startTransition(async()=>{awaitaction();});}}>{children}</button>);}

Read more about using Transitions with Suspense.

Note

Transitions only “wait” long enough to avoid hidingalready revealed content (like the tab container). If the Posts tab had anested<Suspense> boundary, the Transition would not “wait” for it.


Building a Suspense-enabled router

If you’re building a React framework or a router, we recommend marking page navigations as Transitions.

functionRouter(){
const[page,setPage] =useState('/');
const[isPending,startTransition] =useTransition();

functionnavigate(url){
startTransition(()=>{
setPage(url);
});
}
// ...

This is recommended for three reasons:

Here is a simplified router example using Transitions for navigations.

Fork
import{Suspense,useState,useTransition}from'react';importIndexPagefrom'./IndexPage.js';importArtistPagefrom'./ArtistPage.js';importLayoutfrom'./Layout.js';exportdefaultfunctionApp(){return(<Suspensefallback={<BigSpinner/>}><Router/></Suspense>);}functionRouter(){const[page,setPage] =useState('/');const[isPending,startTransition] =useTransition();functionnavigate(url){startTransition(()=>{setPage(url);});}letcontent;if(page ==='/'){content =(<IndexPagenavigate={navigate}/>);}elseif(page ==='/the-beatles'){content =(<ArtistPageartist={{id:'the-beatles',name:'The Beatles',}}/>);}return(<LayoutisPending={isPending}>{content}</Layout>);}functionBigSpinner(){return<h2>🌀 Loading...</h2>;}

Note

Suspense-enabled routers are expected to wrap the navigation updates into Transitions by default.


Displaying an error to users with an error boundary

If a function passed tostartTransition throws an error, you can display an error to your user with anerror boundary. To use an error boundary, wrap the component where you are calling theuseTransition in an error boundary. Once the function passed tostartTransition errors, the fallback for the error boundary will be displayed.

Fork
import{useTransition}from"react";import{ErrorBoundary}from"react-error-boundary";exportfunctionAddCommentContainer(){return(<ErrorBoundaryfallback={<p>⚠️Something went wrong</p>}><AddCommentButton/></ErrorBoundary>);}functionaddComment(comment){// For demonstration purposes to show Error Boundaryif(comment ==null){thrownewError("Example Error: An error thrown to trigger error boundary");}}functionAddCommentButton(){const[pending,startTransition] =useTransition();return(<buttondisabled={pending}onClick={()=>{startTransition(()=>{// Intentionally not passing a comment// so error gets thrownaddComment();});}}>      Add comment</button>);}

Troubleshooting

Updating an input in a Transition doesn’t work

You can’t use a Transition for a state variable that controls an input:

const[text,setText] =useState('');
// ...
functionhandleChange(e){
// ❌ Can't use Transitions for controlled input state
startTransition(()=>{
setText(e.target.value);
});
}
// ...
return<inputvalue={text}onChange={handleChange}/>;

This is because Transitions are non-blocking, but updating an input in response to the change event should happen synchronously. If you want to run a Transition in response to typing, you have two options:

  1. You can declare two separate state variables: one for the input state (which always updates synchronously), and one that you will update in a Transition. This lets you control the input using the synchronous state, and pass the Transition state variable (which will “lag behind” the input) to the rest of your rendering logic.
  2. Alternatively, you can have one state variable, and adduseDeferredValue which will “lag behind” the real value. It will trigger non-blocking re-renders to “catch up” with the new value automatically.

React doesn’t treat my state update as a Transition

When you wrap a state update in a Transition, make sure that it happensduring thestartTransition call:

startTransition(()=>{
// ✅ Setting state *during* startTransition call
setPage('/about');
});

The function you pass tostartTransition must be synchronous. You can’t mark an update as a Transition like this:

startTransition(()=>{
// ❌ Setting state *after* startTransition call
setTimeout(()=>{
setPage('/about');
},1000);
});

Instead, you could do this:

setTimeout(()=>{
startTransition(()=>{
// ✅ Setting state *during* startTransition call
setPage('/about');
});
},1000);

React doesn’t treat my state update afterawait as a Transition

When you useawait inside astartTransition function, the state updates that happen after theawait are not marked as Transitions. You must wrap state updates after eachawait in astartTransition call:

startTransition(async()=>{
awaitsomeAsyncFunction();
// ❌ Not using startTransition after await
setPage('/about');
});

However, this works instead:

startTransition(async()=>{
awaitsomeAsyncFunction();
// ✅ Using startTransition *after* await
startTransition(()=>{
setPage('/about');
});
});

This is a JavaScript limitation due to React losing the scope of the async context. In the future, whenAsyncContext is available, this limitation will be removed.


I want to calluseTransition from outside a component

You can’t calluseTransition outside a component because it’s a Hook. In this case, use the standalonestartTransition method instead. It works the same way, but it doesn’t provide theisPending indicator.


The function I pass tostartTransition executes immediately

If you run this code, it will print 1, 2, 3:

console.log(1);
startTransition(()=>{
console.log(2);
setPage('/about');
});
console.log(3);

It is expected to print 1, 2, 3. The function you pass tostartTransition does not get delayed. Unlike with the browsersetTimeout, it does not run the callback later. React executes your function immediately, but any state updates scheduledwhile it is running are marked as Transitions. You can imagine that it works like this:

// A simplified version of how React works

letisInsideTransition =false;

functionstartTransition(scope){
isInsideTransition =true;
scope();
isInsideTransition =false;
}

functionsetState(){
if(isInsideTransition){
// ... schedule a Transition state update ...
}else{
// ... schedule an urgent state update ...
}
}

My state updates in Transitions are out of order

If youawait insidestartTransition, you might see the updates happen out of order.

In this example, theupdateQuantity function simulates a request to the server to update the item’s quantity in the cart. This functionartificially returns every other request after the previous to simulate race conditions for network requests.

Try updating the quantity once, then update it quickly multiple times. You might see the incorrect total:

Fork
import{useState,useTransition}from"react";import{updateQuantity}from"./api";importItemfrom"./Item";importTotalfrom"./Total";exportdefaultfunctionApp({}){const[quantity,setQuantity] =useState(1);const[isPending,startTransition] =useTransition();// Store the actual quantity in separate state to show the mismatch.const[clientQuantity,setClientQuantity] =useState(1);constupdateQuantityAction =newQuantity=>{setClientQuantity(newQuantity);// Access the pending state of the transition,// by wrapping in startTransition again.startTransition(async()=>{constsavedQuantity =awaitupdateQuantity(newQuantity);startTransition(()=>{setQuantity(savedQuantity);});});};return(<div><h1>Checkout</h1><Itemaction={updateQuantityAction}/><hr/><TotalclientQuantity={clientQuantity}savedQuantity={quantity}isPending={isPending}/></div>);}

When clicking multiple times, it’s possible for previous requests to finish after later requests. When this happens, React currently has no way to know the intended order. This is because the updates are scheduled asynchronously, and React loses context of the order across the async boundary.

This is expected, because Actions within a Transition do not guarantee execution order. For common use cases, React provides higher-level abstractions likeuseActionState and<form> actions that handle ordering for you. For advanced use cases, you’ll need to implement your own queuing and abort logic to handle this.

Example ofuseActionState handling execution order:

Fork
import{useState,useActionState}from"react";import{updateQuantity}from"./api";importItemfrom"./Item";importTotalfrom"./Total";exportdefaultfunctionApp({}){// Store the actual quantity in separate state to show the mismatch.const[clientQuantity,setClientQuantity] =useState(1);const[quantity,updateQuantityAction,isPending] =useActionState(async(prevState,payload)=>{setClientQuantity(payload);constsavedQuantity =awaitupdateQuantity(payload);returnsavedQuantity;// Return the new quantity to update the state},1// Initial quantity);return(<div><h1>Checkout</h1><Itemaction={updateQuantityAction}/><hr/><TotalclientQuantity={clientQuantity}savedQuantity={quantity}isPending={isPending}/></div>);}


[8]ページ先頭

©2009-2025 Movatter.jp