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