← Back

useTransition for Pending UI

isPending for delete buttons, nested startTransition for state updates after await.

PublishedMarch 5, 2026123 words
Edit

useTransition

useTransition marks state updates as non-urgent, keeping your UI responsive during expensive operations.

Example: DeletePostButton

From app/dashboard/[slug]/_components/DeletePostButton.tsx:

'use client'; import { useTransition } from 'react'; export function DeletePostButton({ slug }) { const router = useRouter(); const [isPending, startTransition] = useTransition(); function deleteAction() { startTransition(async () => { await deletePost(slug); router.push('/dashboard'); }); } return ( <button onClick={deleteAction} disabled={isPending}> {isPending ? 'Deleting...' : 'Delete'} </button> ); }
'use client'; import { useTransition } from 'react'; export function DeletePostButton({ slug }) { const router = useRouter(); const [isPending,

When clicked, isPending becomes true immediately and stays true until the Server Function and navigation complete.

Caveat: State Updates After Await

State updates after await need nested startTransition:

startTransition(async () => { await someAsyncFunction(); startTransition(() => { setState('done'); }); // ✅ }

In the delete example, router.push handles this internally.

startTransition
]
=
useTransition
(
)
;
function
deleteAction
(
)
{
startTransition
(
async
(
)
=>
{
await
deletePost
(
slug
)
;
router
.
push
(
'/dashboard'
)
;
}
)
;
}
return
(
<
button
onClick
=
{
deleteAction
}
disabled
=
{
isPending
}
>
{
isPending
?
'Deleting...'
:
'Delete'
}
</
button
>
)
;
}
)
;
startTransition(async () => { await someAsyncFunction(); startTransition(() => { setState('done'); }); // ✅ });