[fail] Only warn on unacted effects for strict / non sync modes (#16041)

* only warn on unacted effects for strict / non sync modes

(basically, when `fiber.mode !== 0b0000`)

Warnings on unacted effects may be too noisy, especially for legacy apps. This PR fires the warning only when in a non sync mode (concurrent/batched), or when in strict mode. This should make updating easier.

I also added batched mode tests to the act() suite.

* explicitly check for modes before warning, explicit tests for all modes
This commit is contained in:
Sunil Pai 2019-07-03 01:29:45 +01:00 committed by GitHub
parent 6b946ad9da
commit bd846459d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 26 deletions

View File

@ -53,32 +53,23 @@ describe('ReactDOMHooks', () => {
return 3 * n;
}
// we explicitly catch the missing act() warnings
// to simulate this tricky repro
expect(() => {
ReactDOM.render(<Example1 n={1} />, container);
expect(container.textContent).toBe('1');
expect(container2.textContent).toBe('');
expect(container3.textContent).toBe('');
Scheduler.unstable_flushAll();
expect(container.textContent).toBe('1');
expect(container2.textContent).toBe('2');
expect(container3.textContent).toBe('3');
ReactDOM.render(<Example1 n={1} />, container);
expect(container.textContent).toBe('1');
expect(container2.textContent).toBe('');
expect(container3.textContent).toBe('');
Scheduler.unstable_flushAll();
expect(container.textContent).toBe('1');
expect(container2.textContent).toBe('2');
expect(container3.textContent).toBe('3');
ReactDOM.render(<Example1 n={2} />, container);
expect(container.textContent).toBe('2');
expect(container2.textContent).toBe('2'); // Not flushed yet
expect(container3.textContent).toBe('3'); // Not flushed yet
Scheduler.unstable_flushAll();
expect(container.textContent).toBe('2');
expect(container2.textContent).toBe('4');
expect(container3.textContent).toBe('6');
}).toWarnDev([
'An update to Example1 ran an effect',
'An update to Example2 ran an effect',
'An update to Example1 ran an effect',
'An update to Example2 ran an effect',
]);
ReactDOM.render(<Example1 n={2} />, container);
expect(container.textContent).toBe('2');
expect(container2.textContent).toBe('2'); // Not flushed yet
expect(container3.textContent).toBe('3'); // Not flushed yet
Scheduler.unstable_flushAll();
expect(container.textContent).toBe('2');
expect(container2.textContent).toBe('4');
expect(container3.textContent).toBe('6');
});
it('should not bail out when an update is scheduled from within an event handler', () => {

View File

@ -48,6 +48,73 @@ describe('ReactTestUtils.act()', () => {
ReactDOM.unmountComponentAtNode(dom);
}
runActTests('legacy sync mode', renderSync, unmountSync);
// and then in batched mode
let batchedRoot;
function renderBatched(el, dom) {
batchedRoot = ReactDOM.unstable_createSyncRoot(dom);
batchedRoot.render(el);
}
function unmountBatched(dom) {
if (batchedRoot !== null) {
batchedRoot.unmount();
batchedRoot = null;
}
}
runActTests('batched mode', renderBatched, unmountBatched);
describe('unacted effects', () => {
function App() {
React.useEffect(() => {}, []);
return null;
}
it('does not warn in legacy sync mode', () => {
expect(() => {
ReactDOM.render(<App />, document.createElement('div'));
}).toWarnDev([]);
});
it('warns in strict mode', () => {
expect(() => {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.createElement('div'),
);
}).toWarnDev([
'An update to App ran an effect, but was not wrapped in act(...)',
'An update to App ran an effect, but was not wrapped in act(...)',
]);
});
it('warns in batched mode', () => {
expect(() => {
const root = ReactDOM.unstable_createSyncRoot(
document.createElement('div'),
);
root.render(<App />);
Scheduler.unstable_flushAll();
}).toWarnDev([
'An update to App ran an effect, but was not wrapped in act(...)',
'An update to App ran an effect, but was not wrapped in act(...)',
]);
});
it('warns in concurrent mode', () => {
expect(() => {
const root = ReactDOM.unstable_createRoot(
document.createElement('div'),
);
root.render(<App />);
Scheduler.unstable_flushAll();
}).toWarnDev([
'An update to App ran an effect, but was not wrapped in act(...)',
'An update to App ran an effect, but was not wrapped in act(...)',
]);
});
});
});
function runActTests(label, render, unmount) {

View File

@ -61,6 +61,7 @@ import {
import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
import {
NoMode,
StrictMode,
ProfileMode,
BatchedMode,
ConcurrentMode,
@ -2453,6 +2454,10 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
if (__DEV__) {
if (
warnsIfNotActing === true &&
(fiber.mode & StrictMode ||
fiber.mode & ProfileMode ||
fiber.mode & BatchedMode ||
fiber.mode & ConcurrentMode) &&
IsSomeRendererActing.current === false &&
IsThisRendererActing.current === false
) {

View File

@ -1806,7 +1806,6 @@ describe('ReactHooks', () => {
globalListener();
globalListener();
}).toWarnDev([
'An update to C ran an effect',
'An update to C inside a test was not wrapped in act',
'An update to C inside a test was not wrapped in act',
// Note: should *not* warn about updates on unmounted component.