Proposal: global.IS_REACT_ACT_ENVIRONMENT
#102
Replies: 3 comments 5 replies
-
Thanks for writing this up. The background was helpful to understand the warning better for React 18 specifically. I think in the end almost every usage would use the That being said, I was hoping this proposal addresses the issues with fake vs real timers and
React Testing Library wraps all the relevant APIs in So assuming this proposal makes it through, let's look at a common testing pattern with React Testing Library: Please ignore the specific implementation for data-fetching. We just want something that loads something on click. import { Fragment, useEffect, useState } from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
it('displays content when clicked', async () => {
function App() {
const [showContent, setShowContent] = useState(false);
const [content, setContent] = useState(false);
useEffect(() => {
if (showContent) {
// just mocking some fetch()
setTimeout(() => {
setContent('fetched content')
}, 1000)
}
}, [showContent])
return <Fragment><button onClick={() => setShowContent(true)} />{content}</Fragment>
}
render(<App />);
// This schedules an effect
// Triggers no warning since Testing Library wraps this in `act()`
fireEvent.click(getByRole('button'));
await waitFor(() => expect(screen.getByText('fetched content')).toBeVisible());
}); Historically, this worked with real timers and fake timers. Every RTL API was wrapped in So let's think about the implementation with different timers: real timers fake timers The problem is that now every API needs to be able to detect real vs fake timers not just It all seems like we trade one warning for another warning. Though I may just argue against having to do all this work. I don't think I can fully grasp all the edge cases we may or may not hit. So shipping it behind a flag so that I can play around with it in different repos would help me personally see if this proposal works. At least before I could avoid them since I wasn't using Jest and just opted-in via Regarding the implementation: I would prefer setting this value in the React module itself (e.g. |
Beta Was this translation helpful? Give feedback.
-
This proposal has been implemented and is now available in the latest React 18 alphas |
Beta Was this translation helpful? Give feedback.
-
We noticed that this heuristic is still in place. Was this a mistake? Thanks. |
Beta Was this translation helpful? Give feedback.
-
This proposal has been implemented and is now available in the latest React 18 alphas.
Note: This may or may not be the final name. Will likely go through a round of bikeshedding.
tl;dr
typeof jest === 'undefined'
heuristicBackground: Why does React warn if you don't wrap updates with
act
?act
is the API we use to flush React's asynchronous task queues at the end of some scope of work. Usually, anact
scope corresponds to a browser event like a click or a network response handler:We introduced the
act
API alongside hooks, becauseuseEffect
was the first React API to schedule asynchronous work. We call these "passive" effects, and they fire after the browser has painted.If you were to test a component that contains a
useEffect
, but you didn't useact
in your test, then the passive effects would not fire, and your tests would not match production behavior.In concurrent React, it's not just passive effects — all work happens in an async task (with the exception of
flushSync
), so the problem is more pronounced. You probably wouldn't forget to useact
in the normal case — otherwise, React wouldn't update the screen at all. But it's feasible that you would forget to wrap, say, a mocked network response that happens to resolve a Suspense promise.To prevent this mistake, React fires a warning if it receives an update that hasn't been wrapped with
act
:React only fires this warning if detects that you're in a testing environment. Here's where things get messy...
act
is not the right API for all testsact
is designed for tests where you know exactly which event queues are relevant and you can control them, usually with mocking. In addition to user interactions, common examples include timers and network events:It's not designed for tests where you poll until something changes, like end-to-end (e2e) tests, or React Testing Library's
waitFor
method:In an e2e test, instead of mocking your task queues, you run your code in something resembling a "real" browser environment, and wait until the UI matches what you expect.
The mechanism we currently use to decide whether to warn is pretty clowny: we check if
jest
is globally defined. This has some pretty obvious flaws — for example, tests that don't use Jest don't get any warnings at all.Another flaw is, even if you do use Jest, you might be writing an e2e test. But doesn't make sense to use
act
in an e2e test. So why does React warn about it?There are other tests where
act
isn't necessary, like our tests that mock the Scheduler package. React shouldn't warn about those, either.So we need new a new strategy. We added the global
jest
check as a temporary hack to unblock ourselves, but never got around to replacing it with something better.Make
act
an opt-in testing patternMy proposal is to provide an API for users to tell React that they are writing
act
-style tests. I don't know what to call it yet, but let's go with this for now:React would read this global to decide whether to fire
act
warnings.Users typically would not set this directly; it'd be configured somewhere in your testing infra, like a Jest plugin or an assertion library.
For example, you might enable
act
warnings for the scope of a single function:You could also do the inverse: disable
act
warnings for the scope of a single test.Or you might enable
act
warnings for all the tests in a file:Because you have to opt into this configuration, tests that don't use
act
won't trigger spurious warnings.What if users do use
act
but forget to opt into warnings?We'll warn about it :)
If you do use
act
, butIS_REACT_ACT_ENVIRONMENT
isn'ttrue
, then we will warn you to update your testing infra.Except in legacy roots (ReactDOM.render), since otherwise we'd break existing tests.
Beta Was this translation helpful? Give feedback.
All reactions