This rule raises an issue when a call to fireEvent, userEvent, or render is placed directly inside the callback of waitFor.

Why is this an issue?

waitFor retries its callback on an interval and on every DOM mutation observed in its container, until the callback stops throwing or a timeout is reached; the callback runs immediately and then repeats, so it can execute an unknown number of times before resolving.

Placing a side effect such as fireEvent, userEvent, or render directly inside that callback means the side effect reruns on every retry, not just once. A click, a key press, a form submission, or a re-render happening several times instead of once can change what the test actually exercises, hide a real bug behind a passing assertion, or make the test intermittently fail depending on how many retries occur.

Move the side effect out of the callback to fix this: call it once before waitFor, or run it inside a .then() chained after waitFor() if it must happen only once the wait resolves. A side effect placed inside such a .then() is not affected by this issue, since it only runs once after the wait resolves.

Retrying a side effect this way is rarely intentional, so there is no accepted trade-off to weigh here. If a click, a submission, or a render genuinely needs to happen more than once, issue those calls explicitly outside waitFor instead of relying on its retry behavior, so the number of times they run stays visible and does not depend on retry timing.

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

test('submits the form', async () => {
  render(<Form />);

  await waitFor(() => {
    fireEvent.click(screen.getByRole('button', { name: 'Submit' })); // Noncompliant: the click can be repeated every time this callback is retried
  });

  expect(screen.getByText('Success')).toBeInTheDocument();
});
import { render, screen, fireEvent, waitFor } from '@testing-library/react';

test('submits the form', async () => {
  render(<Form />);

  fireEvent.click(screen.getByRole('button', { name: 'Submit' }));

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

Resources

Documentation

Articles & blog posts