mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
allow nested act()s from different renderers (#16039)
* allow nested `act()`s from different renderers There are usecases where multiple renderers need to oprate inside an act() scope - ReactDOM.render being used inside another component tree. The parent component will be rendered using ReactTestRenderer.create for a snapshot test or something. - a ReactDOM instance interacting with a ReactTestRenderer instance (like for the new devtools) This PR changes the way the acting sigils operate to allow for this. It keeps 2 booleans, one attached to React, one attached to the renderer. act() changes these values, and the workloop reads them to decide what warning to trigger. I also renamed shouldWarnUnactedUpdates to warnsIfNotActing * s/ReactIsActing/IsSomeRendererActing and s/ReactRendererIsActing/IsThisRendererActing
This commit is contained in:
parent
a865e4a642
commit
a457e02ae3
|
|
@ -70,10 +70,7 @@ it('warns when using the wrong act version - test + dom: updates', () => {
|
|||
TestRenderer.act(() => {
|
||||
setCtr(1);
|
||||
});
|
||||
}).toWarnDev([
|
||||
'An update to Counter inside a test was not wrapped in act',
|
||||
"It looks like you're using the wrong act()",
|
||||
]);
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"]);
|
||||
});
|
||||
|
||||
it('warns when using the wrong act version - dom + test: .create()', () => {
|
||||
|
|
@ -109,10 +106,7 @@ it('warns when using the wrong act version - dom + test: updates', () => {
|
|||
TestUtils.act(() => {
|
||||
setCtr(1);
|
||||
});
|
||||
}).toWarnDev([
|
||||
'An update to Counter inside a test was not wrapped in act',
|
||||
"It looks like you're using the wrong act()",
|
||||
]);
|
||||
}).toWarnDev(["It looks like you're using the wrong act()"]);
|
||||
});
|
||||
|
||||
const {Surface, Group, Shape} = ReactART;
|
||||
|
|
@ -158,3 +152,11 @@ it('does not warn when nesting react-act inside react-test-renderer', () => {
|
|||
TestRenderer.create(<ARTTest />);
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't warn if you use nested acts from different renderers", () => {
|
||||
TestRenderer.act(() => {
|
||||
TestUtils.act(() => {
|
||||
TestRenderer.create(<App />);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
2
packages/react-art/src/ReactARTHostConfig.js
vendored
2
packages/react-art/src/ReactARTHostConfig.js
vendored
|
|
@ -347,7 +347,7 @@ export function shouldSetTextContent(type, props) {
|
|||
export const isPrimaryRenderer = false;
|
||||
|
||||
// The ART renderer shouldn't trigger missing act() warnings
|
||||
export const shouldWarnUnactedUpdates = false;
|
||||
export const warnsIfNotActing = false;
|
||||
|
||||
export const supportsMutation = true;
|
||||
|
||||
|
|
|
|||
4
packages/react-dom/src/client/ReactDOM.js
vendored
4
packages/react-dom/src/client/ReactDOM.js
vendored
|
|
@ -38,7 +38,7 @@ import {
|
|||
findHostInstance,
|
||||
findHostInstanceWithWarning,
|
||||
flushPassiveEffects,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
} from 'react-reconciler/inline.dom';
|
||||
import {createPortal as createPortalImpl} from 'shared/ReactPortal';
|
||||
import {canUseDOM} from 'shared/ExecutionEnvironment';
|
||||
|
|
@ -817,7 +817,7 @@ const ReactDOM: Object = {
|
|||
dispatchEvent,
|
||||
runEventsInBatch,
|
||||
flushPassiveEffects,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -348,7 +348,7 @@ export function createTextInstance(
|
|||
}
|
||||
|
||||
export const isPrimaryRenderer = true;
|
||||
export const shouldWarnUnactedUpdates = true;
|
||||
export const warnsIfNotActing = true;
|
||||
// This initialization code may run even on server environments
|
||||
// if a component just imports ReactDOM (e.g. for findDOMNode).
|
||||
// Some environments might not have setTimeout or clearTimeout.
|
||||
|
|
|
|||
4
packages/react-dom/src/fire/ReactFire.js
vendored
4
packages/react-dom/src/fire/ReactFire.js
vendored
|
|
@ -43,7 +43,7 @@ import {
|
|||
findHostInstance,
|
||||
findHostInstanceWithWarning,
|
||||
flushPassiveEffects,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
} from 'react-reconciler/inline.fire';
|
||||
import {createPortal as createPortalImpl} from 'shared/ReactPortal';
|
||||
import {canUseDOM} from 'shared/ExecutionEnvironment';
|
||||
|
|
@ -823,7 +823,7 @@ const ReactDOM: Object = {
|
|||
dispatchEvent,
|
||||
runEventsInBatch,
|
||||
flushPassiveEffects,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const [
|
|||
runEventsInBatch,
|
||||
/* eslint-disable no-unused-vars */
|
||||
flushPassiveEffects,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
/* eslint-enable no-unused-vars */
|
||||
] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ const [
|
|||
runEventsInBatch,
|
||||
/* eslint-enable no-unused-vars */
|
||||
flushPassiveEffects,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events;
|
||||
|
||||
const batchedUpdates = ReactDOM.unstable_batchedUpdates;
|
||||
|
||||
const {ReactCurrentActingRendererSigil} = ReactSharedInternals;
|
||||
const {IsSomeRendererActing} = ReactSharedInternals;
|
||||
|
||||
// this implementation should be exactly the same in
|
||||
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
|
||||
|
|
@ -86,17 +86,21 @@ let actingUpdatesScopeDepth = 0;
|
|||
|
||||
function act(callback: () => Thenable) {
|
||||
let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
||||
let previousActingUpdatesSigil;
|
||||
let previousIsSomeRendererActing;
|
||||
let previousIsThisRendererActing;
|
||||
actingUpdatesScopeDepth++;
|
||||
if (__DEV__) {
|
||||
previousActingUpdatesSigil = ReactCurrentActingRendererSigil.current;
|
||||
ReactCurrentActingRendererSigil.current = ReactActingRendererSigil;
|
||||
previousIsSomeRendererActing = IsSomeRendererActing.current;
|
||||
previousIsThisRendererActing = IsSomeRendererActing.current;
|
||||
IsSomeRendererActing.current = true;
|
||||
IsThisRendererActing.current = true;
|
||||
}
|
||||
|
||||
function onDone() {
|
||||
actingUpdatesScopeDepth--;
|
||||
if (__DEV__) {
|
||||
ReactCurrentActingRendererSigil.current = previousActingUpdatesSigil;
|
||||
IsSomeRendererActing.current = previousIsSomeRendererActing;
|
||||
IsThisRendererActing.current = previousIsThisRendererActing;
|
||||
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
|
||||
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
|
||||
warningWithoutStack(
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
|
|||
export const isPrimaryRenderer = false;
|
||||
|
||||
// The Fabric renderer shouldn't trigger missing act() warnings
|
||||
export const shouldWarnUnactedUpdates = false;
|
||||
export const warnsIfNotActing = false;
|
||||
|
||||
export const scheduleTimeout = setTimeout;
|
||||
export const cancelTimeout = clearTimeout;
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ export function resetAfterCommit(containerInfo: Container): void {
|
|||
}
|
||||
|
||||
export const isPrimaryRenderer = true;
|
||||
export const shouldWarnUnactedUpdates = true;
|
||||
export const warnsIfNotActing = true;
|
||||
|
||||
export const scheduleTimeout = setTimeout;
|
||||
export const cancelTimeout = clearTimeout;
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ type TextInstance = {|
|
|||
|};
|
||||
type HostContext = Object;
|
||||
|
||||
const {ReactCurrentActingRendererSigil} = ReactSharedInternals;
|
||||
const {IsSomeRendererActing} = ReactSharedInternals;
|
||||
|
||||
const NO_CONTEXT = {};
|
||||
const UPPERCASE_CONTEXT = {};
|
||||
|
|
@ -393,7 +393,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
|||
now: Scheduler.unstable_now,
|
||||
|
||||
isPrimaryRenderer: true,
|
||||
shouldWarnUnactedUpdates: true,
|
||||
warnsIfNotActing: true,
|
||||
supportsHydration: false,
|
||||
|
||||
mountEventComponent(): void {
|
||||
|
|
@ -566,7 +566,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
|||
const {
|
||||
flushPassiveEffects,
|
||||
batchedUpdates,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
} = NoopRenderer;
|
||||
|
||||
// this act() implementation should be exactly the same in
|
||||
|
|
@ -615,17 +615,21 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
|||
|
||||
function act(callback: () => Thenable) {
|
||||
let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
||||
let previousActingUpdatesSigil;
|
||||
let previousIsSomeRendererActing;
|
||||
let previousIsThisRendererActing;
|
||||
actingUpdatesScopeDepth++;
|
||||
if (__DEV__) {
|
||||
previousActingUpdatesSigil = ReactCurrentActingRendererSigil.current;
|
||||
ReactCurrentActingRendererSigil.current = ReactActingRendererSigil;
|
||||
previousIsSomeRendererActing = IsSomeRendererActing.current;
|
||||
previousIsThisRendererActing = IsSomeRendererActing.current;
|
||||
IsSomeRendererActing.current = true;
|
||||
IsThisRendererActing.current = true;
|
||||
}
|
||||
|
||||
function onDone() {
|
||||
actingUpdatesScopeDepth--;
|
||||
if (__DEV__) {
|
||||
ReactCurrentActingRendererSigil.current = previousActingUpdatesSigil;
|
||||
IsSomeRendererActing.current = previousIsSomeRendererActing;
|
||||
IsThisRendererActing.current = previousIsThisRendererActing;
|
||||
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
|
||||
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
|
||||
warningWithoutStack(
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ import {
|
|||
flushDiscreteUpdates,
|
||||
flushPassiveEffects,
|
||||
warnIfNotScopedWithMatchingAct,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
} from './ReactFiberWorkLoop';
|
||||
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
|
||||
import ReactFiberInstrumentation from './ReactFiberInstrumentation';
|
||||
|
|
@ -345,7 +345,7 @@ export {
|
|||
flushControlled,
|
||||
flushSync,
|
||||
flushPassiveEffects,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
};
|
||||
|
||||
export function getPublicRootInstance(
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ import {
|
|||
scheduleTimeout,
|
||||
cancelTimeout,
|
||||
noTimeout,
|
||||
shouldWarnUnactedUpdates,
|
||||
warnsIfNotActing,
|
||||
} from './ReactFiberHostConfig';
|
||||
|
||||
import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber';
|
||||
|
|
@ -174,7 +174,7 @@ const ceil = Math.ceil;
|
|||
const {
|
||||
ReactCurrentDispatcher,
|
||||
ReactCurrentOwner,
|
||||
ReactCurrentActingRendererSigil,
|
||||
IsSomeRendererActing,
|
||||
} = ReactSharedInternals;
|
||||
|
||||
type ExecutionContext = number;
|
||||
|
|
@ -2420,18 +2420,14 @@ function warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber) {
|
|||
}
|
||||
}
|
||||
|
||||
// We export a simple object here to be used by a renderer/test-utils
|
||||
// as the value of ReactCurrentActingRendererSigil.current
|
||||
// This identity lets us identify (ha!) when the wrong renderer's act()
|
||||
// wraps anothers' updates/effects
|
||||
export const ReactActingRendererSigil = {};
|
||||
export const IsThisRendererActing = {current: (false: boolean)};
|
||||
|
||||
export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
|
||||
if (__DEV__) {
|
||||
if (
|
||||
shouldWarnUnactedUpdates === true &&
|
||||
ReactCurrentActingRendererSigil.current !== null &&
|
||||
ReactCurrentActingRendererSigil.current !== ReactActingRendererSigil
|
||||
warnsIfNotActing === true &&
|
||||
IsSomeRendererActing.current === true &&
|
||||
IsThisRendererActing.current !== true
|
||||
) {
|
||||
warningWithoutStack(
|
||||
false,
|
||||
|
|
@ -2456,8 +2452,9 @@ export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
|
|||
export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
|
||||
if (__DEV__) {
|
||||
if (
|
||||
shouldWarnUnactedUpdates === true &&
|
||||
ReactCurrentActingRendererSigil.current !== ReactActingRendererSigil
|
||||
warnsIfNotActing === true &&
|
||||
IsSomeRendererActing.current === false &&
|
||||
IsThisRendererActing.current === false
|
||||
) {
|
||||
warningWithoutStack(
|
||||
false,
|
||||
|
|
@ -2482,9 +2479,10 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
|
|||
function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
|
||||
if (__DEV__) {
|
||||
if (
|
||||
shouldWarnUnactedUpdates === true &&
|
||||
warnsIfNotActing === true &&
|
||||
executionContext === NoContext &&
|
||||
ReactCurrentActingRendererSigil.current !== ReactActingRendererSigil
|
||||
IsSomeRendererActing.current === false &&
|
||||
IsThisRendererActing.current === false
|
||||
) {
|
||||
warningWithoutStack(
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export const cancelTimeout = $$$hostConfig.clearTimeout;
|
|||
export const noTimeout = $$$hostConfig.noTimeout;
|
||||
export const now = $$$hostConfig.now;
|
||||
export const isPrimaryRenderer = $$$hostConfig.isPrimaryRenderer;
|
||||
export const shouldWarnUnactedUpdates = $$$hostConfig.shouldWarnUnactedUpdates;
|
||||
export const warnsIfNotActing = $$$hostConfig.warnsIfNotActing;
|
||||
export const supportsMutation = $$$hostConfig.supportsMutation;
|
||||
export const supportsPersistence = $$$hostConfig.supportsPersistence;
|
||||
export const supportsHydration = $$$hostConfig.supportsHydration;
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ export function createTextInstance(
|
|||
}
|
||||
|
||||
export const isPrimaryRenderer = false;
|
||||
export const shouldWarnUnactedUpdates = true;
|
||||
export const warnsIfNotActing = true;
|
||||
|
||||
export const scheduleTimeout = setTimeout;
|
||||
export const cancelTimeout = clearTimeout;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type {Thenable} from 'react-reconciler/src/ReactFiberWorkLoop';
|
|||
import {
|
||||
batchedUpdates,
|
||||
flushPassiveEffects,
|
||||
ReactActingRendererSigil,
|
||||
IsThisRendererActing,
|
||||
} from 'react-reconciler/inline.test';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
|
@ -19,7 +19,7 @@ import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
|
|||
import enqueueTask from 'shared/enqueueTask';
|
||||
import * as Scheduler from 'scheduler';
|
||||
|
||||
const {ReactCurrentActingRendererSigil} = ReactSharedInternals;
|
||||
const {IsSomeRendererActing} = ReactSharedInternals;
|
||||
|
||||
// this implementation should be exactly the same in
|
||||
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
|
||||
|
|
@ -67,17 +67,21 @@ let actingUpdatesScopeDepth = 0;
|
|||
|
||||
function act(callback: () => Thenable) {
|
||||
let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
|
||||
let previousActingUpdatesSigil;
|
||||
let previousIsSomeRendererActing;
|
||||
let previousIsThisRendererActing;
|
||||
actingUpdatesScopeDepth++;
|
||||
if (__DEV__) {
|
||||
previousActingUpdatesSigil = ReactCurrentActingRendererSigil.current;
|
||||
ReactCurrentActingRendererSigil.current = ReactActingRendererSigil;
|
||||
previousIsSomeRendererActing = IsSomeRendererActing.current;
|
||||
previousIsThisRendererActing = IsSomeRendererActing.current;
|
||||
IsSomeRendererActing.current = true;
|
||||
IsThisRendererActing.current = true;
|
||||
}
|
||||
|
||||
function onDone() {
|
||||
actingUpdatesScopeDepth--;
|
||||
if (__DEV__) {
|
||||
ReactCurrentActingRendererSigil.current = previousActingUpdatesSigil;
|
||||
IsSomeRendererActing.current = previousIsSomeRendererActing;
|
||||
IsThisRendererActing.current = previousIsThisRendererActing;
|
||||
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
|
||||
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
|
||||
warningWithoutStack(
|
||||
|
|
|
|||
17
packages/react/src/IsSomeRendererActing.js
Normal file
17
packages/react/src/IsSomeRendererActing.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used by act() to track whether you're inside an act() scope.
|
||||
*/
|
||||
|
||||
const IsSomeRendererActing = {
|
||||
current: (false: boolean),
|
||||
};
|
||||
export default IsSomeRendererActing;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used by act() to track whether you're outside an act() scope.
|
||||
* We use a renderer's flushPassiveEffects as the sigil value
|
||||
* so we can track identity of the renderer.
|
||||
*/
|
||||
|
||||
const ReactCurrentActingRendererSigil = {
|
||||
current: (null: null | (() => boolean)),
|
||||
};
|
||||
export default ReactCurrentActingRendererSigil;
|
||||
|
|
@ -10,13 +10,13 @@ import ReactCurrentDispatcher from './ReactCurrentDispatcher';
|
|||
import ReactCurrentBatchConfig from './ReactCurrentBatchConfig';
|
||||
import ReactCurrentOwner from './ReactCurrentOwner';
|
||||
import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
|
||||
import ReactCurrentActingRendererSigil from './ReactCurrentActingRendererSigil';
|
||||
import IsSomeRendererActing from './IsSomeRendererActing';
|
||||
|
||||
const ReactSharedInternals = {
|
||||
ReactCurrentDispatcher,
|
||||
ReactCurrentBatchConfig,
|
||||
ReactCurrentOwner,
|
||||
ReactCurrentActingRendererSigil,
|
||||
IsSomeRendererActing,
|
||||
// Used by renderers to avoid bundling object-assign twice in UMD bundles:
|
||||
assign,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user