Skip to content

Commit 7c64c30

Browse files
committed
allow nested acts from different renderers
sometimes apps can be made wtih multiple renderers. a specific usecase is react-art embedded inside react-dom. in these cases, we want to be able to wrap an update with acts corrsponding to both the renderers. In this PR, we change ReactCurrentActingRendererSigil to hold a stack of sigils (instead of just the current active one), pushing and popping onto it as required. This commit also fixes the dom fixtures tests, which were broken? ugh.
1 parent a383c46 commit 7c64c30

File tree

6 files changed

+69
-60
lines changed

6 files changed

+69
-60
lines changed

fixtures/dom/src/index.test.js

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,12 @@ import React from 'react';
1111
import ReactDOM from 'react-dom';
1212
import TestUtils from 'react-dom/test-utils';
1313
import TestRenderer from 'react-test-renderer';
14+
global.__DEV__ = process.env.NODE_ENV !== 'production';
1415

15-
let spy;
16-
beforeEach(() => {
17-
spy = jest.spyOn(console, 'error').mockImplementation(() => {});
16+
expect.extend({
17+
...require('../../../scripts/jest/matchers/toWarnDev'),
1818
});
1919

20-
function confirmWarning() {
21-
expect(spy).toHaveBeenCalledWith(
22-
expect.stringContaining(
23-
"It looks like you're using the wrong act() around your test interactions."
24-
),
25-
''
26-
);
27-
}
28-
2920
function App(props) {
3021
return 'hello world';
3122
}
@@ -34,29 +25,33 @@ it("doesn't warn when you use the right act + renderer: dom", () => {
3425
TestUtils.act(() => {
3526
TestUtils.renderIntoDocument(<App />);
3627
});
37-
expect(spy).not.toHaveBeenCalled();
3828
});
3929

4030
it("doesn't warn when you use the right act + renderer: test", () => {
4131
TestRenderer.act(() => {
4232
TestRenderer.create(<App />);
4333
});
44-
expect(spy).not.toHaveBeenCalled();
4534
});
4635

47-
it('works with createRoot().render combo', () => {
36+
it('warns when using createRoot() + .render', () => {
4837
const root = ReactDOM.unstable_createRoot(document.createElement('div'));
49-
TestRenderer.act(() => {
50-
root.render(<App />);
38+
expect(() => {
39+
TestRenderer.act(() => {
40+
root.render(<App />);
41+
});
42+
}).toWarnDev(["It looks like you're using the wrong act()"], {
43+
withoutStack: true,
5144
});
52-
confirmWarning();
5345
});
5446

5547
it('warns when using the wrong act version - test + dom: render', () => {
56-
TestRenderer.act(() => {
57-
TestUtils.renderIntoDocument(<App />);
48+
expect(() => {
49+
TestRenderer.act(() => {
50+
TestUtils.renderIntoDocument(<App />);
51+
});
52+
}).toWarnDev(["It looks like you're using the wrong act()"], {
53+
withoutStack: true,
5854
});
59-
confirmWarning();
6055
});
6156

6257
it('warns when using the wrong act version - test + dom: updates', () => {
@@ -67,29 +62,35 @@ it('warns when using the wrong act version - test + dom: updates', () => {
6762
return ctr;
6863
}
6964
TestUtils.renderIntoDocument(<Counter />);
70-
TestRenderer.act(() => {
71-
setCtr(1);
72-
});
73-
confirmWarning();
65+
expect(() => {
66+
TestRenderer.act(() => {
67+
setCtr(1);
68+
});
69+
}).toWarnDev([
70+
'An update to Counter inside a test was not wrapped in act',
71+
"It looks like you're using the wrong act()",
72+
]);
7473
});
7574

7675
it('warns when using the wrong act version - dom + test: .create()', () => {
77-
TestUtils.act(() => {
78-
TestRenderer.create(<App />);
76+
expect(() => {
77+
TestUtils.act(() => {
78+
TestRenderer.create(<App />);
79+
});
80+
}).toWarnDev(["It looks like you're using the wrong act()"], {
81+
withoutStack: true,
7982
});
80-
confirmWarning();
8183
});
8284

8385
it('warns when using the wrong act version - dom + test: .update()', () => {
84-
let root;
85-
// use the right one here so we don't get the first warning
86-
TestRenderer.act(() => {
87-
root = TestRenderer.create(<App key="one" />);
88-
});
89-
TestUtils.act(() => {
90-
root.update(<App key="two" />);
86+
const root = TestRenderer.create(<App key="one" />);
87+
expect(() => {
88+
TestUtils.act(() => {
89+
root.update(<App key="two" />);
90+
});
91+
}).toWarnDev(["It looks like you're using the wrong act()"], {
92+
withoutStack: true,
9193
});
92-
confirmWarning();
9394
});
9495

9596
it('warns when using the wrong act version - dom + test: updates', () => {
@@ -100,8 +101,20 @@ it('warns when using the wrong act version - dom + test: updates', () => {
100101
return ctr;
101102
}
102103
const root = TestRenderer.create(<Counter />);
103-
TestUtils.act(() => {
104-
setCtr(1);
104+
expect(() => {
105+
TestUtils.act(() => {
106+
setCtr(1);
107+
});
108+
}).toWarnDev([
109+
'An update to Counter inside a test was not wrapped in act',
110+
"It looks like you're using the wrong act()",
111+
]);
112+
});
113+
114+
it("doesn't warn if you use nested acts from different renderers", () => {
115+
TestRenderer.act(() => {
116+
TestUtils.act(() => {
117+
TestRenderer.create(<App />);
118+
});
105119
});
106-
confirmWarning();
107120
});

packages/react-dom/src/test-utils/ReactTestUtilsAct.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,15 @@ let actingUpdatesScopeDepth = 0;
8686

8787
function act(callback: () => Thenable) {
8888
let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
89-
let previousActingUpdatesSigil;
9089
actingUpdatesScopeDepth++;
9190
if (__DEV__) {
92-
previousActingUpdatesSigil = ReactCurrentActingRendererSigil.current;
93-
ReactCurrentActingRendererSigil.current = ReactActingRendererSigil;
91+
ReactCurrentActingRendererSigil.current.push(ReactActingRendererSigil);
9492
}
9593

9694
function onDone() {
9795
actingUpdatesScopeDepth--;
9896
if (__DEV__) {
99-
ReactCurrentActingRendererSigil.current = previousActingUpdatesSigil;
97+
ReactCurrentActingRendererSigil.current.pop();
10098
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
10199
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
102100
warningWithoutStack(

packages/react-noop-renderer/src/createReactNoop.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -702,17 +702,15 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
702702

703703
function act(callback: () => Thenable) {
704704
let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
705-
let previousActingUpdatesSigil;
706705
actingUpdatesScopeDepth++;
707706
if (__DEV__) {
708-
previousActingUpdatesSigil = ReactCurrentActingRendererSigil.current;
709-
ReactCurrentActingRendererSigil.current = ReactActingRendererSigil;
707+
ReactCurrentActingRendererSigil.current.push(ReactActingRendererSigil);
710708
}
711709

712710
function onDone() {
713711
actingUpdatesScopeDepth--;
714712
if (__DEV__) {
715-
ReactCurrentActingRendererSigil.current = previousActingUpdatesSigil;
713+
ReactCurrentActingRendererSigil.current.pop();
716714
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
717715
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
718716
warningWithoutStack(

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2387,10 +2387,10 @@ export const ReactActingRendererSigil = {};
23872387
export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void {
23882388
if (__DEV__) {
23892389
if (
2390-
ReactCurrentActingRendererSigil.current !== null &&
2391-
// use the function flushPassiveEffects directly as the sigil
2392-
// so this comparison is expected here
2393-
ReactCurrentActingRendererSigil.current !== ReactActingRendererSigil
2390+
ReactCurrentActingRendererSigil.current.length !== 0 &&
2391+
ReactCurrentActingRendererSigil.current.indexOf(
2392+
ReactActingRendererSigil,
2393+
) === -1
23942394
) {
23952395
// it looks like we're using the wrong matching act(), so log a warning
23962396
warningWithoutStack(
@@ -2417,7 +2417,9 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
24172417
if (__DEV__) {
24182418
if (
24192419
executionContext === NoContext &&
2420-
ReactCurrentActingRendererSigil.current !== ReactActingRendererSigil
2420+
ReactCurrentActingRendererSigil.current.indexOf(
2421+
ReactActingRendererSigil,
2422+
) === -1
24212423
) {
24222424
warningWithoutStack(
24232425
false,

packages/react-test-renderer/src/ReactTestRendererAct.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,15 @@ let actingUpdatesScopeDepth = 0;
6767

6868
function act(callback: () => Thenable) {
6969
let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
70-
let previousActingUpdatesSigil;
7170
actingUpdatesScopeDepth++;
7271
if (__DEV__) {
73-
previousActingUpdatesSigil = ReactCurrentActingRendererSigil.current;
74-
ReactCurrentActingRendererSigil.current = ReactActingRendererSigil;
72+
ReactCurrentActingRendererSigil.current.push(ReactActingRendererSigil);
7573
}
7674

7775
function onDone() {
7876
actingUpdatesScopeDepth--;
7977
if (__DEV__) {
80-
ReactCurrentActingRendererSigil.current = previousActingUpdatesSigil;
78+
ReactCurrentActingRendererSigil.current.pop();
8179
if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
8280
// if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
8381
warningWithoutStack(

packages/react/src/ReactCurrentActingRendererSigil.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
*/
99

1010
/**
11-
* Used by act() to track whether you're outside an act() scope.
12-
* We use a renderer's flushPassiveEffects as the sigil value
13-
* so we can track identity of the renderer.
11+
* We maintain a 'stack' of renderer specific sigils
12+
* corresponding to act() calls, so we can track whenever an update
13+
* happens inside/outside of one.
1414
*/
1515

1616
const ReactCurrentActingRendererSigil = {
17-
current: (null: null | (() => boolean)),
17+
current: ([]: Array<{}>),
1818
};
1919
export default ReactCurrentActingRendererSigil;

0 commit comments

Comments
 (0)