How to Test useTransition Hook in React
This article provides a straightforward guide on how to test React’s
useTransition hook using React Testing Library and Jest.
You will learn how to handle the concurrent rendering behavior of
transitions, assert the temporary pending state, and verify the final
rendered output once the transition completes.
Understanding useTransition in Tests
The useTransition hook splits state updates into
high-priority and low-priority (transition) phases. When a transition is
active, React sets the isPending flag to true
while rendering the background update, then sets it back to
false when the update completes.
To test this behavior successfully, your tests must capture two distinct moments: 1. The pending state (immediately after the event triggers). 2. The completed state (after the concurrent rendering finishes).
Because React Testing Library (RTL) automatically handles concurrent rendering updates, you do not need special configuration. However, you must use asynchronous assertions to wait for the transition to settle.
Example Component
Below is a standard component that utilizes
useTransition to filter a large list without blocking the
user input:
import { useState, useTransition } from 'react';
export function SearchFilter({ items }) {
const [isPending, startTransition] = useTransition();
const [filter, setFilter] = useState('');
const handleChange = (event) => {
startTransition(() => {
setFilter(event.target.value);
});
};
return (
<div>
<input
type="text"
onChange={handleChange}
placeholder="Search items..."
/>
{isPending && <p>Updating list...</p>}
<ul>
{items
.filter((item) => item.toLowerCase().includes(filter.toLowerCase()))
.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}Writing the Test
To test the code above, use @testing-library/react and
@testing-library/user-event. The test should simulate user
typing, assert that the “Updating list…” message briefly appears, and
then assert that the message disappears and the filtered results are
displayed.
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SearchFilter } from './SearchFilter';
test('renders pending state and updates results during transition', async () => {
const user = userEvent.setup();
const items = ['Apple', 'Banana', 'Cherry'];
render(<SearchFilter items={items} />);
const input = screen.getByPlaceholderText('Search items...');
// Trigger the transition by typing into the input
await user.type(input, 'Banana');
// 1. Assert the immediate pending state
expect(screen.getByText('Updating list...')).toBeInTheDocument();
// 2. Wait for the transition to finish and the pending state to disappear
await waitFor(() => {
expect(screen.queryByText('Updating list...')).not.toBeInTheDocument();
});
// 3. Assert the final UI state
expect(screen.getByText('Banana')).toBeInTheDocument();
expect(screen.queryByText('Apple')).not.toBeInTheDocument();
});Key Testing Practices for useTransition
- Use
user-event: Always use@testing-library/user-eventinstead offireEvent.userEventsimulates realistic browser interactions, which ensures React’s scheduler processes the transition priority correctly. - Assert both states: Do not just test the final
state. Explicitly assert the presence of your loading indicator or
isPendingUI state, then usewaitFororfindByqueries to await its removal. - Avoid wrapping in manual
act()calls: Modern React Testing Library utilities are already wrapped inact. When usingawait user.type()orwaitFor, the React scheduler will advance correctly without throwing missingact(...)warnings.