Skip to content

Commit

Permalink
warn if passive effects get queued outside of an act() call.
Browse files Browse the repository at this point in the history
based on facebook#15591. of note, we don't modify our own tests to satisfy this warning, instead using a feature flag to disable the warning for some tests. this is because we're testing the actual sequencing of work in these tests, and don't want to flush everything with act().
  • Loading branch information
Sunil Pai committed May 12, 2019
1 parent 6920e9f commit 9f7a346
Show file tree
Hide file tree
Showing 20 changed files with 81 additions and 10 deletions.
Expand Up @@ -14,11 +14,14 @@ let React;
let ReactTestRenderer;
let Scheduler;
let ReactDebugTools;
let ReactFeatureFlags;
let act;

describe('ReactHooksInspectionIntegration', () => {
beforeEach(() => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;
React = require('react');
ReactTestRenderer = require('react-test-renderer');
Scheduler = require('scheduler');
Expand Down
3 changes: 3 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMHooks-test.js
Expand Up @@ -11,6 +11,7 @@

let React;
let ReactDOM;
let ReactFeatureFlags;
let Scheduler;

describe('ReactDOMHooks', () => {
Expand All @@ -22,6 +23,8 @@ describe('ReactDOMHooks', () => {
React = require('react');
ReactDOM = require('react-dom');
Scheduler = require('scheduler');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;

container = document.createElement('div');
document.body.appendChild(container);
Expand Down
Expand Up @@ -38,6 +38,7 @@ function initModules() {

ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
Expand Down
Expand Up @@ -43,6 +43,7 @@ describe('ReactErrorBoundaries', () => {
PropTypes = require('prop-types');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;
ReactDOM = require('react-dom');
React = require('react');
Scheduler = require('scheduler');
Expand Down
3 changes: 3 additions & 0 deletions packages/react-dom/src/__tests__/ReactUpdates-test.js
Expand Up @@ -12,6 +12,7 @@
let React;
let ReactDOM;
let ReactTestUtils;
let ReactFeatureFlags;
let Scheduler;

describe('ReactUpdates', () => {
Expand All @@ -20,6 +21,8 @@ describe('ReactUpdates', () => {
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;
Scheduler = require('scheduler');
});

Expand Down
25 changes: 23 additions & 2 deletions packages/react-reconciler/src/ReactFiberHooks.js
Expand Up @@ -33,7 +33,8 @@ import {
computeExpirationForFiber,
flushPassiveEffects,
requestCurrentTime,
warnIfNotCurrentlyActingUpdatesInDev,
warnIfNotCurrentlyActingEffectsInDEV,
warnIfNotCurrentlyActingUpdatesInDEV,
markRenderEventTime,
} from './ReactFiberScheduler';

Expand Down Expand Up @@ -869,6 +870,16 @@ function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
if (__DEV__) {
// jest isn't a 'global', it's just exposed to tests via a wrapped function
// further, this isn't a test file, so flow doesn't recognize the symbol. So...
// $FlowExpectedError - because requirements don't give a damn about your type sigs.
if ('undefined' !== typeof jest) {
warnIfNotCurrentlyActingEffectsInDEV(
((currentlyRenderingFiber: any): Fiber),
);
}
}
return mountEffectImpl(
UpdateEffect | PassiveEffect,
UnmountPassive | MountPassive,
Expand All @@ -881,6 +892,16 @@ function updateEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
if (__DEV__) {
// jest isn't a 'global', it's just exposed to tests via a wrapped function
// further, this isn't a test file, so flow doesn't recognize the symbol. So...
// $FlowExpectedError - because requirements don't give a damn about your type sigs.
if ('undefined' !== typeof jest) {
warnIfNotCurrentlyActingEffectsInDEV(
((currentlyRenderingFiber: any): Fiber),
);
}
}
return updateEffectImpl(
UpdateEffect | PassiveEffect,
UnmountPassive | MountPassive,
Expand Down Expand Up @@ -1180,7 +1201,7 @@ function dispatchAction<S, A>(
// further, this isn't a test file, so flow doesn't recognize the symbol. So...
// $FlowExpectedError - because requirements don't give a damn about your type sigs.
if ('undefined' !== typeof jest) {
warnIfNotCurrentlyActingUpdatesInDev(fiber);
warnIfNotCurrentlyActingUpdatesInDEV(fiber);
}
}
scheduleWork(fiber, expirationTime);
Expand Down
30 changes: 27 additions & 3 deletions packages/react-reconciler/src/ReactFiberScheduler.js
Expand Up @@ -24,6 +24,7 @@ import {
enableProfilerTimer,
disableYielding,
enableSchedulerTracing,
warnAboutUnactedEffectsinDEV,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import invariant from 'shared/invariant';
Expand Down Expand Up @@ -2088,7 +2089,32 @@ function warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber) {
}
}

function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
if (__DEV__) {
if (warnAboutUnactedEffectsinDEV) {
if (ReactShouldWarnActingUpdates.current === false) {
warningWithoutStack(
false,
'An effect from %s inside a test was not wrapped in act(...).\n\n' +
'When testing, code that causes React state updates should be ' +
'wrapped into act(...):\n\n' +
'act(() => {\n' +
' /* fire events that update state */\n' +
'});\n' +
'/* assert on the output */\n\n' +
"This ensures that you're testing the behavior the user would see " +
'in the browser.' +
' Learn more at https://fb.me/react-wrap-tests-with-act' +
'%s',
getComponentName(fiber.type),
getStackByFiberInDevAndProd(fiber),
);
}
}
}
}

export function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
if (__DEV__) {
if (
workPhase === NotWorking &&
Expand All @@ -2114,8 +2140,6 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
}
}

export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;

let componentsWithSuspendedDiscreteUpdates = null;
export function checkForWrongSuspensePriorityInDEV(sourceFiber: Fiber) {
if (__DEV__) {
Expand Down
Expand Up @@ -27,6 +27,7 @@ describe('ReactHooks', () => {

ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;
React = require('react');
ReactTestRenderer = require('react-test-renderer');
Scheduler = require('scheduler');
Expand Down
Expand Up @@ -39,6 +39,7 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.enableSchedulerTracing = true;
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
Expand Down
Expand Up @@ -20,6 +20,7 @@ describe('ReactIncrementalScheduling', () => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
Expand Down
Expand Up @@ -20,6 +20,7 @@ describe('ReactIncrementalUpdates', () => {
jest.resetModuleRegistry();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
Expand Down
Expand Up @@ -26,6 +26,7 @@ describe('ReactSchedulerIntegration', () => {
jest.resetModules();
ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
ReactFeatureFlags.warnAboutUnactedEffectsinDEV = false;
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
Expand Down
8 changes: 3 additions & 5 deletions packages/react/src/__tests__/withComponentStack-test.js
Expand Up @@ -44,7 +44,6 @@ describe('withComponentStack', () => {
let React = null;
let ReactTestRenderer = null;
let error = null;
let scheduler = null;
let warn = null;

beforeEach(() => {
Expand All @@ -53,7 +52,6 @@ describe('withComponentStack', () => {

React = require('react');
ReactTestRenderer = require('react-test-renderer');
scheduler = require('scheduler');

error = React.error;
warn = React.warn;
Expand Down Expand Up @@ -179,9 +177,9 @@ describe('withComponentStack', () => {
return null;
}

ReactTestRenderer.create(<Parent />);

scheduler.flushAll(); // Flush passive effects
ReactTestRenderer.act(() => {
ReactTestRenderer.create(<Parent />);
});

expectMessageAndStack(
'logged in child render method',
Expand Down
5 changes: 5 additions & 0 deletions packages/shared/ReactFeatureFlags.js
Expand Up @@ -71,3 +71,8 @@ export const enableJSXTransformAPI = false;
// We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v17?)
// Till then, we warn about the missing mock, but still fallback to a sync mode compatible version
export const warnAboutMissingMockScheduler = false;

// We warn if effects are queued in tests without a surround act()
// However, this interferes withour internal tests that test the actual
// sequencing work. So we use a feature flag to disable the warning *only* for ourselves
export const warnAboutUnactedEffectsinDEV = true;
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-fb.js
Expand Up @@ -33,6 +33,7 @@ export const warnAboutDeprecatedSetNativeProps = true;
export const enableEventAPI = false;
export const enableJSXTransformAPI = false;
export const warnAboutMissingMockScheduler = true;
export const warnAboutUnactedEffectsinDEV = true;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-oss.js
Expand Up @@ -30,6 +30,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
export const enableEventAPI = false;
export const enableJSXTransformAPI = false;
export const warnAboutMissingMockScheduler = false;
export const warnAboutUnactedEffectsinDEV = true;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.persistent.js
Expand Up @@ -30,6 +30,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
export const enableEventAPI = false;
export const enableJSXTransformAPI = false;
export const warnAboutMissingMockScheduler = true;
export const warnAboutUnactedEffectsinDEV = true;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.test-renderer.js
Expand Up @@ -30,6 +30,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
export const enableEventAPI = false;
export const enableJSXTransformAPI = false;
export const warnAboutMissingMockScheduler = false;
export const warnAboutUnactedEffectsinDEV = true;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
Expand Up @@ -28,6 +28,7 @@ export const disableYielding = false;
export const enableEventAPI = true;
export const enableJSXTransformAPI = true;
export const warnAboutMissingMockScheduler = true;
export const warnAboutUnactedEffectsinDEV = true;

// Only used in www builds.
export function addUserTimingListener() {
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/forks/ReactFeatureFlags.www.js
Expand Up @@ -73,6 +73,8 @@ export const enableJSXTransformAPI = true;

export const warnAboutMissingMockScheduler = true;

export const warnAboutUnactedEffectsinDEV = true;

// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
type Check<_X, Y: _X, X: Y = _X> = null;
Expand Down

0 comments on commit 9f7a346

Please sign in to comment.