mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
flush only on exiting outermost act() (#15682)
This commit is contained in:
+31
-18
@@ -137,6 +137,26 @@ function runActTests(label, render, unmount) {
|
||||
expect(container.innerHTML).toBe('5');
|
||||
});
|
||||
|
||||
it('should flush effects only on exiting the outermost act', () => {
|
||||
function App() {
|
||||
React.useEffect(() => {
|
||||
Scheduler.yieldValue(0);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
// let's nest a couple of act() calls
|
||||
act(() => {
|
||||
act(() => {
|
||||
render(<App />, container);
|
||||
});
|
||||
// the effect wouldn't have yielded yet because
|
||||
// we're still inside an act() scope
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
});
|
||||
// but after exiting the last one, effects get flushed
|
||||
expect(Scheduler).toHaveYielded([0]);
|
||||
});
|
||||
|
||||
it('warns if a setState is called outside of act(...)', () => {
|
||||
let setValue = null;
|
||||
function App() {
|
||||
@@ -281,7 +301,7 @@ function runActTests(label, render, unmount) {
|
||||
});
|
||||
});
|
||||
describe('asynchronous tests', () => {
|
||||
it('can handle timers', async () => {
|
||||
it('works with timeouts', async () => {
|
||||
function App() {
|
||||
let [ctr, setCtr] = React.useState(0);
|
||||
function doSomething() {
|
||||
@@ -295,16 +315,17 @@ function runActTests(label, render, unmount) {
|
||||
}, []);
|
||||
return ctr;
|
||||
}
|
||||
act(() => {
|
||||
render(<App />, container);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
render(<App />, container);
|
||||
// flush a little to start the timer
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await sleep(100);
|
||||
});
|
||||
expect(container.innerHTML).toBe('1');
|
||||
});
|
||||
|
||||
it('can handle async/await', async () => {
|
||||
it('flushes microtasks before exiting', async () => {
|
||||
function App() {
|
||||
let [ctr, setCtr] = React.useState(0);
|
||||
async function someAsyncFunction() {
|
||||
@@ -321,10 +342,7 @@ function runActTests(label, render, unmount) {
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
render(<App />, container);
|
||||
});
|
||||
// pending promises will close before this ends
|
||||
render(<App />, container);
|
||||
});
|
||||
expect(container.innerHTML).toEqual('1');
|
||||
});
|
||||
@@ -361,7 +379,7 @@ function runActTests(label, render, unmount) {
|
||||
}
|
||||
});
|
||||
|
||||
it('commits and effects are guaranteed to be flushed', async () => {
|
||||
it('async commits and effects are guaranteed to be flushed', async () => {
|
||||
function App() {
|
||||
let [state, setState] = React.useState(0);
|
||||
async function something() {
|
||||
@@ -378,17 +396,12 @@ function runActTests(label, render, unmount) {
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
render(<App />, container);
|
||||
});
|
||||
expect(container.innerHTML).toBe('0');
|
||||
expect(Scheduler).toHaveYielded([0]);
|
||||
render(<App />, container);
|
||||
});
|
||||
// this may seem odd, but it matches user behaviour -
|
||||
// a flash of "0" followed by "1"
|
||||
// exiting act() drains effects and microtasks
|
||||
|
||||
expect(Scheduler).toHaveYielded([0, 1]);
|
||||
expect(container.innerHTML).toBe('1');
|
||||
expect(Scheduler).toHaveYielded([1]);
|
||||
});
|
||||
|
||||
it('propagates errors', async () => {
|
||||
|
||||
+15
-5
@@ -84,16 +84,15 @@ function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
|
||||
let actingUpdatesScopeDepth = 0;
|
||||
|
||||
function act(callback: () => Thenable) {
|
||||
let previousActingUpdatesScopeDepth;
|
||||
let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
||||
actingUpdatesScopeDepth++;
|
||||
if (__DEV__) {
|
||||
previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
||||
actingUpdatesScopeDepth++;
|
||||
ReactShouldWarnActingUpdates.current = true;
|
||||
}
|
||||
|
||||
function onDone() {
|
||||
actingUpdatesScopeDepth--;
|
||||
if (__DEV__) {
|
||||
actingUpdatesScopeDepth--;
|
||||
if (actingUpdatesScopeDepth === 0) {
|
||||
ReactShouldWarnActingUpdates.current = false;
|
||||
}
|
||||
@@ -143,6 +142,13 @@ function act(callback: () => Thenable) {
|
||||
called = true;
|
||||
result.then(
|
||||
() => {
|
||||
if (actingUpdatesScopeDepth > 1) {
|
||||
onDone();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
// we're about to exit the act() scope,
|
||||
// now's the time to flush tasks/effects
|
||||
flushWorkAndMicroTasks((err: ?Error) => {
|
||||
onDone();
|
||||
if (err) {
|
||||
@@ -171,7 +177,11 @@ function act(callback: () => Thenable) {
|
||||
|
||||
// flush effects until none remain, and cleanup
|
||||
try {
|
||||
flushWork();
|
||||
if (actingUpdatesScopeDepth === 1) {
|
||||
// we're about to exit the act() scope,
|
||||
// now's the time to flush effects
|
||||
flushWork();
|
||||
}
|
||||
onDone();
|
||||
} catch (err) {
|
||||
onDone();
|
||||
|
||||
+15
-5
@@ -697,16 +697,15 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
let actingUpdatesScopeDepth = 0;
|
||||
|
||||
function act(callback: () => Thenable) {
|
||||
let previousActingUpdatesScopeDepth;
|
||||
let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
||||
actingUpdatesScopeDepth++;
|
||||
if (__DEV__) {
|
||||
previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
||||
actingUpdatesScopeDepth++;
|
||||
ReactShouldWarnActingUpdates.current = true;
|
||||
}
|
||||
|
||||
function onDone() {
|
||||
actingUpdatesScopeDepth--;
|
||||
if (__DEV__) {
|
||||
actingUpdatesScopeDepth--;
|
||||
if (actingUpdatesScopeDepth === 0) {
|
||||
ReactShouldWarnActingUpdates.current = false;
|
||||
}
|
||||
@@ -756,6 +755,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
called = true;
|
||||
result.then(
|
||||
() => {
|
||||
if (actingUpdatesScopeDepth > 1) {
|
||||
onDone();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
// we're about to exit the act() scope,
|
||||
// now's the time to flush tasks/effects
|
||||
flushWorkAndMicroTasks((err: ?Error) => {
|
||||
onDone();
|
||||
if (err) {
|
||||
@@ -784,7 +790,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
|
||||
// flush effects until none remain, and cleanup
|
||||
try {
|
||||
flushWork();
|
||||
if (actingUpdatesScopeDepth === 1) {
|
||||
// we're about to exit the act() scope,
|
||||
// now's the time to flush effects
|
||||
flushWork();
|
||||
}
|
||||
onDone();
|
||||
} catch (err) {
|
||||
onDone();
|
||||
|
||||
+15
-5
@@ -65,16 +65,15 @@ function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) {
|
||||
let actingUpdatesScopeDepth = 0;
|
||||
|
||||
function act(callback: () => Thenable) {
|
||||
let previousActingUpdatesScopeDepth;
|
||||
let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
||||
actingUpdatesScopeDepth++;
|
||||
if (__DEV__) {
|
||||
previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
||||
actingUpdatesScopeDepth++;
|
||||
ReactShouldWarnActingUpdates.current = true;
|
||||
}
|
||||
|
||||
function onDone() {
|
||||
actingUpdatesScopeDepth--;
|
||||
if (__DEV__) {
|
||||
actingUpdatesScopeDepth--;
|
||||
if (actingUpdatesScopeDepth === 0) {
|
||||
ReactShouldWarnActingUpdates.current = false;
|
||||
}
|
||||
@@ -124,6 +123,13 @@ function act(callback: () => Thenable) {
|
||||
called = true;
|
||||
result.then(
|
||||
() => {
|
||||
if (actingUpdatesScopeDepth > 1) {
|
||||
onDone();
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
// we're about to exit the act() scope,
|
||||
// now's the time to flush tasks/effects
|
||||
flushWorkAndMicroTasks((err: ?Error) => {
|
||||
onDone();
|
||||
if (err) {
|
||||
@@ -152,7 +158,11 @@ function act(callback: () => Thenable) {
|
||||
|
||||
// flush effects until none remain, and cleanup
|
||||
try {
|
||||
flushWork();
|
||||
if (actingUpdatesScopeDepth === 1) {
|
||||
// we're about to exit the act() scope,
|
||||
// now's the time to flush effects
|
||||
flushWork();
|
||||
}
|
||||
onDone();
|
||||
} catch (err) {
|
||||
onDone();
|
||||
|
||||
Reference in New Issue
Block a user