-
Notifications
You must be signed in to change notification settings - Fork 47k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Umbrella: act #15472
Comments
Here's a scenario that gets harder with the Let's take a component, and test it function sleep(period) {
return new Promise(resolve => setTimeout(resolve, period));
}
function App() {
let [counter, setCounter] = useState(0);
async function tick() {
await sleep(500);
setCounter(x => x + 1);
}
useEffect(() => {
tick();
}, [Math.min(counter, 5)]);
return counter === 5 ? <div id="target" /> : null;
} So, this component waits a half second, increments a counter, 5 times in total. On the final tick, our target element pops into view. With our current setup, we can test it like so - const dom = document.createElement("div");
document.body.appendChild(dom);
await act(async () => {
render(<App />, dom);
});
await act(async () => {
await sleep(2500)
expect(document.getElementById('target')).toExist();
}); This looks nice. It's a bit of cheating, because the initial render fires off the chain reaction, and we open up another Now assume effects only flush with Scheduler.flushAll() await act(() => {
render(<App />, dom);
}); At this point, we've flushed effects once, but need to somehow
What does that code look like? |
This test is simpler with a mutationobserver based helper like react-testing-library's render(<App />, dom);
expect(await waitForElement('#target')).toExist() But this approach has the same problem as above, since effects/updates wouldn't be getting flushed while it waits. |
another thing - do you think we should warn on nested act()s now? maybe we should just disallow it else the behaviour isn't super intuitive |
I have PRs up for all the critical ones. Punting on the last 2 - one of those warnings isn't critical, and I'm not fully convinced for making awaiting an act() mandatory. Let's discuss in our secret super villain lair. |
added an item for making nested acts from different renderers to work. |
going to close this issue since I'm tracking it elsewhere. |
@threepointone Where is "elsewhere"? Since it seems to be the blocking item for |
All the 16.9.0 items are done, the remaining are for the future. |
@leoselig You can try |
When calls to act are nested, effects and component updates are only flushed when the outer `act` call returns, as per [1] and [2]. This is convenient for creating helper functions which may invoke `act` themselves. [1] facebook/react#15682 [2] facebook/react#15472
When calls to act are nested, effects and component updates are only flushed when the outer `act` call returns, as per [1] and [2]. This is convenient for creating helper functions which may invoke `act` themselves. [1] facebook/react#15682 [2] facebook/react#15472
Action items
Scheduler.flushAll
to flush pending Scheduler work.act()
- s / flushPassiveEffects / Scheduler.unstable_flushWithoutYielding #15591act
should not flush anything until the outermostact
call exits (except for the updates that always flush early likeflushSync
and serial events). flush only on exiting outermost act() #15682act
warning to React DOM'sroot.update()
(sincecreateRoot
is a new API). using the wrong renderer's act() should warn #15756act
(e.g. a DOM update nested inside Test Renderer'sact
), regardless of whether the update was triggered by a legacy API. using the wrong renderer's act() should warn #15756act
, regardless of whether the update was triggered by a legacy API (e.g.this.setState
orReactDOM.render
) warn if passive effects get queued outside of an act() call. #15763act
s from different renderers should work (eg - a react-art update inside a react-dom tree shouldn't warn allow nestedact()
s from different renderers #15816act
should force pending fallbacks to commit at the end, ignoring how much time has passed, without affecting unrelated timers.act
should warn if it's called from inside a React event handler or React effect/lifecycle.act
should have the same behavior regardless of whether the result is awaited.Discussion
await
will not be batched, but they shouldn't fire the warning. We should still wait to flush passive effects, Scheduler, and microtasks until the end.Scheduler.flushAll
. That means we don't need to callflushPassiveEffects
separately in order to flush them. However, we currently use the return value offlushPassiveEffects
to determine if additional passive effects were scheduled. So perhaps we should export a method likehasPendingEffects
instead.act
even if the handler is synchronous, because that ensures that any dangling microtasks are flushed before the test proceeds. However, it's hard to fire a warning if the user neglects to do this, because such a warning needs to happen in an async task, and the test could exit before the async task fires. The warning is also controversial because of the additional boilerplate. But regardless of whether we fire a warning, we should stick to our recommendation to always awaitact
.act
exits before flushing anything.act
. For the remaining cases, our suggestion is to switch to the Batched Mode API.act
"depth" because nestedact
s are a no-op in Batched Mode.Idiomatic examples
Single event handler
Using a testing framework
where
simulate
is imported from a testing framework and looks something like:Advanced: Multiple events that occur in sequence
In Batched Mode, these would all be flushed in a single batch, so we group them together with an outer
act
.The text was updated successfully, but these errors were encountered: