How to Use waitFor in React Testing Library

I still remember the first time I sat puzzled in front of a failing React test. Everything looked right. The button click worked. The API mock was returning data. But for some reason, the element I expected to appear just… didn’t. Or at least, not in time for the test to catch it. That was my introduction to the frustrating yet eye-opening world of asynchronous UI testing—and the hero that eventually saved the day? waitFor from React Testing Library.

If you’ve ever dealt with dynamic content or asynchronous events in your React apps (and let’s be honest, who hasn’t?), then understanding waitFor is essential. In this post, I’ll walk you through why you need it, how to use it, and common mistakes I’ve personally made so you can avoid them.


The Problem: Timing Is Everything

Let’s say you’re testing a component that fetches user data from an API after clicking a button. Here’s a simplified version:

function UserProfile() {
  const [user, setUser] = React.useState(null);

  const fetchUser = async () => {
    const res = await fetch('/api/user');
    const data = await res.json();
    setUser(data);
  };

  return (
    <div>
      <button onClick={fetchUser}>Load User</button>
      {user && <p>{user.name}</p>}
    </div>
  );
}

Now let’s test it.

import { render, screen, fireEvent } from '@testing-library/react';
import UserProfile from './UserProfile';

test('loads and displays user', async () => {
  render(<UserProfile />);

  fireEvent.click(screen.getByText('Load User'));

  expect(screen.getByText('John Doe')).toBeInTheDocument();
});

Oops. ❌ This will fail. Why? Because the test checks for the user’s name immediately after the button is clicked, not giving the async fetch enough time to complete. Enter waitFor.


What is waitFor?

waitFor is a utility provided by React Testing Library that waits for the provided callback to stop throwing errors before continuing. It’s perfect for waiting on DOM changes that result from async operations.

Here’s the fixed test:

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';

test('loads and displays user', async () => {
  render(<UserProfile />);

  fireEvent.click(screen.getByText('Load User'));

  await waitFor(() => {
    expect(screen.getByText('John Doe')).toBeInTheDocument();
  });
});

Much better. ✅


When Should You Use waitFor?

Use waitFor when:

  • You’re waiting for a UI update that happens asynchronously.
  • You want to assert on a side effect (e.g., data fetching, state update).
  • findBy queries don’t fit the use case.

That said, prefer using findBy when possible. For example:

await screen.findByText('John Doe');

This is cleaner and more intention-revealing for many scenarios. But waitFor offers flexibility when your assertions go beyond just querying elements.


A Real-World Example

Imagine you’re testing a table update after a data fetch. Here’s a real use-case we covered in our blog on React Table Design Ideas. Suppose a search feature updates the table:

await waitFor(() => {
  expect(screen.getByText('Alice')).toBeInTheDocument();
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});

This example shows the power of combining multiple assertions inside one waitFor. It’s especially useful when a UI change is conditional and time-sensitive.


Key Options You Can Pass

waitFor accepts an options object with two main parameters:

  • timeout: How long to wait before throwing an error (default: 1000ms)
  • interval: How frequently to re-run the callback (default: 50ms)

Example:

await waitFor(() => {
  expect(screen.getByText('Done')).toBeInTheDocument();
}, { timeout: 2000, interval: 100 });

Customizing these values can help make your tests more reliable—especially in slow environments.


Common Mistakes I’ve Made (So You Don’t Have To)

❌ Using waitFor with non-throwing callbacks

await waitFor(() => screen.getByText('Success'));

This does nothing because getByText throws if not found, but you’re not asserting anything. Instead:

await waitFor(() => {
  expect(screen.getByText('Success')).toBeInTheDocument();
});

❌ Nesting waitFor inside act

React Testing Library handles act internally. No need to do this:

await act(async () => {
  await waitFor(...);
});

Just write:

await waitFor(...);

❌ Overusing waitFor

Sometimes a findBy does the job cleaner:

await screen.findByText('Welcome');

Use waitFor when you need multiple or complex assertions.


Performance Tips

To make your tests faster and less flaky:

  • Avoid unnecessary waits.
  • Don’t test implementation details.
  • Use mock service workers (MSW) to mock APIs.
  • Prefer findBy and queryBy for better readability.

Bonus: A Custom Utility with waitFor

I often abstract repetitive waits into utilities. For example:

export const waitForLoadingToFinish = () => {
  return waitFor(() => {
    expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
  });
};

Then in tests:

await waitForLoadingToFinish();

Cleaner. DRY. Reusable. ✅


Learning More

Once you get a grip on waitFor, you’ll want to expand your React Testing Library skills further. Check out my post on the Top 10 React Testing Library Queries to go even deeper into testing best practices.

If you’re just getting into React or preparing for interviews, I highly recommend trying our React Interview Quiz to challenge your knowledge.


External Resources

Here are a few official and expert-curated links to further boost your testing skills:

  1. React Testing Library: waitFor Docs
  2. Common Mistakes with React Testing Library

Final Thoughts

Debugging flaky tests is one of the most frustrating parts of frontend development. Understanding how and when to use waitFor will make your tests more reliable, your debugging easier, and your dev life way smoother.

Now that you’ve seen how waitFor fits into real-world scenarios, it’s time to explore more. Whether it’s optimizing table designs or deep-diving into React concepts, we’re always exploring practical code at Decode Fix.

Want to test your broader dev knowledge too? Try your hand at our Complete SQL Quiz and sharpen your backend skills too. 👨‍💻🚀

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top