This warning appears in React Testing Library when asynchronous state updates occur outside of act() calls in tests. It indicates that React state changes are happening without proper test synchronization, which can lead to flaky tests and false positives. The fix involves wrapping async operations with act() or using Testing Library's async utilities.
The `act()` function is React's testing utility that ensures all state updates and side effects are processed before assertions are made. When you see the warning "This error can occur when testing async state updates without wrapping in act()", it means React detected state changes happening outside of an `act()` call during asynchronous operations. This typically occurs when: 1. **Async state updates** happen after a test has "finished" (like promises resolving after `await`) 2. **Timers** (setTimeout, setInterval) trigger state changes 3. **Event handlers** cause state updates asynchronously 4. **API calls** or data fetching completes after test assertions React throws this warning to prevent "flaky tests" - tests that pass or fail randomly depending on timing. Without `act()`, state updates might not be fully processed before assertions run, leading to false negatives (tests failing when they shouldn't) or false positives (tests passing when they shouldn't).
First, locate where the async state update is happening. Common patterns include:
// Example 1: Async function in useEffect
useEffect(() => {
const fetchData = async () => {
const data = await api.getData();
setData(data); // State update happens async
};
fetchData();
}, []);
// Example 2: Event handler with async operation
const handleClick = async () => {
const result = await submitForm();
setResult(result); // State update after async call
};
// Example 3: Timer-based updates
useEffect(() => {
const timer = setTimeout(() => {
setLoaded(true); // State update after timeout
}, 1000);
return () => clearTimeout(timer);
}, []);Look for setState calls inside async functions, .then() callbacks, or timer callbacks.
Use React's act() function from @testing-library/react to wrap asynchronous operations:
import { render, screen, act } from '@testing-library/react';
test('async state update', async () => {
render(<MyComponent />);
// Wrap the async operation in act()
await act(async () => {
// Trigger the async state update
await userEvent.click(screen.getByText('Load Data'));
});
// Now assertions will wait for state updates
expect(screen.getByText('Data loaded')).toBeInTheDocument();
});For timer-based updates:
import { act } from '@testing-library/react';
test('timer update', () => {
jest.useFakeTimers();
render(<ComponentWithTimer />);
act(() => {
jest.advanceTimersByTime(1000); // Advance timer
});
expect(screen.getByText('Timer done')).toBeInTheDocument();
jest.useRealTimers();
});React Testing Library provides utilities that handle act() automatically:
Option A: Use `waitFor` for assertions
import { render, screen, waitFor } from '@testing-library/react';
test('async update with waitFor', async () => {
render(<AsyncComponent />);
// waitFor automatically wraps assertions in act()
await waitFor(() => {
expect(screen.getByText('Data loaded')).toBeInTheDocument();
});
});Option B: Use `findBy` queries
test('async update with findBy', async () => {
render(<AsyncComponent />);
// findBy queries wait for elements to appear
const element = await screen.findByText('Data loaded');
expect(element).toBeInTheDocument();
});Option C: Use `userEvent` with await
import userEvent from '@testing-library/user-event';
test('user interaction with async update', async () => {
const user = userEvent.setup();
render(<FormComponent />);
// userEvent methods return promises
await user.click(screen.getByText('Submit'));
// State updates are handled automatically
expect(screen.getByText('Submitted')).toBeInTheDocument();
});When dealing with promise chains, ensure all state updates are wrapped:
// ❌ Problematic - state update in .then() without act()
fetch('/api/data')
.then(response => response.json())
.then(data => {
setData(data); // Warning: state update outside act()
});
// ✅ Fixed - wrap the entire chain
await act(async () => {
const response = await fetch('/api/data');
const data = await response.json();
setData(data);
});
// ✅ Alternative: extract logic and test separately
const loadData = async () => {
const response = await fetch('/api/data');
const data = await response.json();
setData(data);
};
// In test:
await act(async () => {
await loadData();
});For complex async flows, consider extracting business logic from components to make testing easier.
While fixing tests, you might want to suppress the warning temporarily. Add to your test setup file:
// jest.setup.js or similar
import { act } from '@testing-library/react';
// Suppress act() warnings during development
const originalError = console.error;
beforeAll(() => {
console.error = (...args) => {
if (typeof args[0] === 'string' &&
args[0].includes('Warning: An update to') &&
args[0].includes('inside a test was not wrapped in act')) {
return;
}
originalError.call(console, ...args);
};
});
afterAll(() => {
console.error = originalError;
});Important: This is a temporary measure. Always fix the root cause by properly wrapping async operations with act().
After applying fixes, verify:
1. Warnings are gone: Run tests and check console for act() warnings
2. Tests are stable: Run tests multiple times to ensure no flakiness
3. Assertions pass: All state-dependent assertions should pass consistently
# Run tests with verbose output
npm test -- --verbose
# Or with Jest directly
npx jest --verboseIf warnings persist, use React DevTools or console logs to trace which component is causing the state update outside of act().
### Understanding act() Internals
The act() function does several important things:
1. Flushes effects: Processes all useEffect, useLayoutEffect, and lifecycle methods
2. Updates state: Applies all pending state updates
3. Re-renders: Triggers re-renders for affected components
4. Synchronizes: Ensures everything settles before continuing
### Common Pitfalls
Overusing act(): Don't wrap everything in act(). Only use it when:
- State updates happen asynchronously
- Effects trigger state changes
- Timers or intervals update state
Nested act() calls: Avoid nesting act() calls as it can cause confusion:
// ❌ Avoid
await act(async () => {
await act(async () => {
// Nested act calls
});
});
// ✅ Better
await act(async () => {
// Single act call covering all async operations
});Testing Library v14+ changes: In newer versions, many async utilities automatically wrap operations in act(). Check your version:
npm list @testing-library/react### Performance Considerations
Excessive use of act() can slow down tests. For optimal performance:
1. Group related async operations in a single act() call
2. Use waitFor for assertions instead of multiple act() calls
3. Mock timers with jest.useFakeTimers() for timer-based tests
### Debugging Tips
1. Add console logs: Log before/after state updates to see timing
2. Use React DevTools: Inspect component state during tests
3. Isolate components: Test problematic components in isolation
4. Check async boundaries: Ensure all promise chains are properly awaited
React.FC expects children prop to be defined
React.FC no longer includes implicit children prop
Warning: You provided a `selected` prop to a form field without an `onChange` handler
You provided a 'selected' prop without an onChange handler
Failed to load source map from suspense chunk
How to fix "Failed to load source map from suspense chunk" in React
Prop spreading could cause security issues
Prop spreading could cause security issues
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency