Description
React version: 18.3.1
Steps To Reproduce
Please refer to the CodeSandbox link below for a complete repro.
- Trigger a fetch request by clicking a button that internally calls two setState calls:
setIsLoading(true)
followed bysetIsLoading(false)
once the API call completes. - Use
act
to mimic the behavior in a test using React Testing Library:
<button
onClick={async () => {
act(() => {
handleClick();
});
await act(async () => {
await waitForAPICall();
});
}}
>
Link to code example: https://codesandbox.io/p/devbox/practical-neumann-g82dcv?workspaceId=ws_8edRzHnWwZWJh5UnZUEo7c
The current behavior
-
We see
Trigger onComplete callback for fetch API
is not logged in the console, becausesetIsLoading(true)
andsetIsLoading(false)
are batched into a single render, causing the callback ofuseEffect
not to run. -
This behavior does not match the production app (
act
is not used), where the two state updates trigger separate renders as expected. -
Removing
act
from the logic results in two renders, though it causes a React warning ("Warning: An update to App inside a test was not wrapped in act(...)") in tests.
Additional observations:
-
If I don't await fetch params before the
setIsLoading(true)
call (Noawait getFetchParams()
), the test still causes two re-renders, even insideact
. -
This issue does not occur when upgrading to React 19.1.0.
Based on these observations, it appears that using act
in tests can alter the autobatching behavior in React 18.3.1, potentially reducing test reliability.
The expected behavior
- Using
act
in tests should preserve the same autobatching behavior seen in production. - In this case,
setIsLoading(true)
andsetIsLoading(false)
should result in two separate renders inside tests, just as they do in a real app.