Skip to main content

Rendering Usetransition Loading

Source: .agents/references/coding-standard/vercel-react-best-practices/rules/rendering-usetransition-loading.md

Metadata

  • title: Use useTransition Over Manual Loading States
  • impact: LOW
  • impactDescription: reduces re-renders and improves code clarity
  • tags: rendering, transitions, useTransition, loading, state

Content

Use useTransition Over Manual Loading States

Use useTransition instead of manual useState for loading states. This provides built-in isPending state and automatically manages transitions.

Incorrect (manual loading state):

function SearchResults() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [isLoading, setIsLoading] = useState(false)

const handleSearch = async (value: string) => {
setIsLoading(true)
setQuery(value)
const data = await fetchResults(value)
setResults(data)
setIsLoading(false)
}

return (
<>
<input onChange={(e) => handleSearch(e.target.value)} />
{isLoading && <Spinner />}
<ResultsList results={results} />
</>
)
}

Correct (useTransition with built-in pending state):

import { useTransition, useState } from 'react'

function SearchResults() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [isPending, startTransition] = useTransition()

const handleSearch = (value: string) => {
setQuery(value) // Update input immediately

startTransition(async () => {
// Fetch and update results
const data = await fetchResults(value)
setResults(data)
})
}

return (
<>
<input onChange={(e) => handleSearch(e.target.value)} />
{isPending && <Spinner />}
<ResultsList results={results} />
</>
)
}

Benefits:

  • Automatic pending state: No need to manually manage setIsLoading(true/false)
  • Error resilience: Pending state correctly resets even if the transition throws
  • Better responsiveness: Keeps the UI responsive during updates
  • Interrupt handling: New transitions automatically cancel pending ones

Reference: useTransition