This React Testing Library warning occurs when component state updates happen in tests without proper async handling. The warning indicates untested asynchronous behavior in your components.
This warning appears when React detects state updates or side effects happening in your test environment that weren't properly awaited or wrapped in the act() utility. React's testing utilities require that all component updates be wrapped in act() to ensure the test waits for updates to complete before making assertions. The warning is React's way of telling you that something asynchronous happened in your component that your test didn't account for. This often occurs with useEffect hooks, timers, async API calls, or event handlers that update state. The warning doesn't mean your code is broken in production, but it indicates your test isn't fully capturing the component's behavior, which could lead to flaky or unreliable tests.
React Testing Library provides built-in async utilities that automatically wrap updates in act(). Replace synchronous queries with async alternatives:
// ❌ Bad - synchronous query
const button = screen.getByRole('button');
fireEvent.click(button);
expect(screen.getByText('Success')).toBeInTheDocument();
// ✅ Good - async query with waitFor
const button = screen.getByRole('button');
fireEvent.click(button);
await waitFor(() => {
expect(screen.getByText('Success')).toBeInTheDocument();
});
// ✅ Better - use findBy which includes waiting
const button = screen.getByRole('button');
fireEvent.click(button);
expect(await screen.findByText('Success')).toBeInTheDocument();The findBy queries automatically wait for elements to appear and are wrapped in act().
The userEvent library from @testing-library/user-event provides more realistic user interactions and handles async updates properly:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('handles button click', async () => {
const user = userEvent.setup();
render(<MyComponent />);
// ❌ fireEvent doesn't wait for async updates
// fireEvent.click(screen.getByRole('button'));
// ✅ userEvent properly awaits all updates
await user.click(screen.getByRole('button'));
expect(screen.getByText('Updated')).toBeInTheDocument();
});Always await userEvent methods as they return promises.
For assertions that depend on async state updates, use waitFor() to wait for the condition to be met:
import { render, screen, waitFor } from '@testing-library/react';
test('updates after async operation', async () => {
render(<AsyncComponent />);
// Trigger the async operation
const button = screen.getByRole('button');
await userEvent.click(button);
// Wait for the update to complete
await waitFor(() => {
expect(screen.getByText('Loading complete')).toBeInTheDocument();
});
// Or wait for element to appear
await waitFor(() => {
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});
});waitFor() automatically retries the callback until it succeeds or times out.
If your component uses setTimeout or setInterval, use Jest fake timers to control timing:
import { render, screen, act } from '@testing-library/react';
test('handles delayed state update', async () => {
jest.useFakeTimers();
render(<ComponentWithTimeout />);
// Fast-forward time and flush all pending timers
await act(async () => {
jest.runAllTimers();
});
expect(screen.getByText('Timeout complete')).toBeInTheDocument();
jest.useRealTimers();
});Wrap timer advances in act() to ensure React processes all updates.
Missing cleanup functions can cause state updates after component unmount:
// ❌ Bad - no cleanup, may update after unmount
useEffect(() => {
fetchData().then(data => setData(data));
}, []);
// ✅ Good - cleanup cancels pending updates
useEffect(() => {
let cancelled = false;
fetchData().then(data => {
if (!cancelled) {
setData(data);
}
});
return () => {
cancelled = true;
};
}, []);
// ✅ Also good - with AbortController
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal)
.then(data => setData(data))
.catch(err => {
if (err.name !== 'AbortError') throw err;
});
return () => controller.abort();
}, []);When mocking API calls or async functions, ensure they resolve synchronously in tests or are properly awaited:
import { render, screen, waitFor } from '@testing-library/react';
// Mock the API
jest.mock('../api', () => ({
fetchUser: jest.fn(),
}));
import { fetchUser } from '../api';
test('loads user data', async () => {
// Mock resolves immediately
fetchUser.mockResolvedValue({ name: 'John' });
render(<UserProfile userId="123" />);
// Wait for the data to load
expect(await screen.findByText('John')).toBeInTheDocument();
// Or use waitFor
await waitFor(() => {
expect(screen.getByText('John')).toBeInTheDocument();
});
});For rare cases where you need to trigger updates outside React Testing Library utilities:
import { render, act } from '@testing-library/react';
test('handles external state manager', async () => {
const { container } = render(<Component />);
// Manual state update outside React
await act(async () => {
externalStore.dispatch({ type: 'UPDATE' });
// Wait for any promises to resolve
await new Promise(resolve => setTimeout(resolve, 0));
});
expect(container.textContent).toContain('Updated');
});Only use this as a last resort - prefer React Testing Library's built-in utilities.
The act() warning is actually a helpful signal from React's test renderer, not just noise to suppress. It indicates that your test isn't fully exercising the component's behavior. When you see this warning, it means there's asynchronous work happening that you're not testing. Suppressing the warning without addressing the root cause can lead to race conditions and flaky tests. In React 18 and later, the act() warning has been refined to reduce false positives, but genuine warnings should still be addressed. For complex components with multiple async operations, consider breaking them into smaller, more testable units. Third-party component libraries may trigger act() warnings due to their internal implementation - in these cases, ensure you're using the latest version of both the library and React Testing Library, as compatibility improvements are ongoing. The React team recommends treating act() warnings with the same seriousness as test failures, as they indicate incomplete test coverage of component behavior.
Prop spreading could cause security issues
Prop spreading could cause security issues
Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.
React Hook useEffect placed inside a condition
Hook can only be called inside the body of a function component
Hook can only be called inside the body of a function component
Rollup failed to resolve import during build
How to fix "Rollup failed to resolve import" in React