Skip to content

Commit bafb596

Browse files
committed
act: Resolve to return value of scope function
When migrating some internal tests I found it annoying that I couldn't return anything from the `act` scope. You would have to declare the variable on the outside then assign to it. But this doesn't play well with type systems — when you use the variable, you have to check the type. Before: ```js let renderer; act(() => { renderer = ReactTestRenderer.create(<App />); }) // Type system can't tell that renderer is never undefined renderer?.root.findByType(Component); ``` After: ```js const renderer = await act(() => { return ReactTestRenderer.create(<App />); }) renderer.root.findByType(Component); ```
1 parent e2453e2 commit bafb596

File tree

2 files changed

+43
-9
lines changed

2 files changed

+43
-9
lines changed

packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,35 @@ describe('isomorphic act()', () => {
4747
});
4848
expect(root).toMatchRenderedOutput('B');
4949
});
50+
51+
// @gate __DEV__
52+
test('return value – sync callback', async () => {
53+
expect(await act(() => 'hi')).toEqual('hi');
54+
});
55+
56+
// @gate __DEV__
57+
test('return value – sync callback, nested', async () => {
58+
const returnValue = await act(() => {
59+
return act(() => 'hi');
60+
});
61+
expect(returnValue).toEqual('hi');
62+
});
63+
64+
// @gate __DEV__
65+
test('return value – async callback', async () => {
66+
const returnValue = await act(async () => {
67+
return await Promise.resolve('hi');
68+
});
69+
expect(returnValue).toEqual('hi');
70+
});
71+
72+
// @gate __DEV__
73+
test('return value – async callback, nested', async () => {
74+
const returnValue = await act(async () => {
75+
return await act(async () => {
76+
return await Promise.resolve('hi');
77+
});
78+
});
79+
expect(returnValue).toEqual('hi');
80+
});
5081
});

packages/react/src/ReactAct.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
4848
then(resolve, reject) {
4949
wasAwaited = true;
5050
result.then(
51-
() => {
51+
returnValue => {
5252
popActScope(prevActScopeDepth);
5353
if (actScopeDepth === 0) {
5454
// We've exited the outermost act scope. Recursively flush the
5555
// queue until there's no remaining work.
56-
recursivelyFlushAsyncActWork(resolve, reject);
56+
recursivelyFlushAsyncActWork(returnValue, resolve, reject);
5757
} else {
58-
resolve();
58+
resolve(returnValue);
5959
}
6060
},
6161
error => {
@@ -88,6 +88,7 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
8888
}
8989
return thenable;
9090
} else {
91+
const returnValue = result;
9192
// The callback is not an async function. Exit the current scope
9293
// immediately, without awaiting.
9394
popActScope(prevActScopeDepth);
@@ -108,7 +109,9 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
108109
if (ReactCurrentActQueue.current === null) {
109110
// Recursively flush the queue until there's no remaining work.
110111
ReactCurrentActQueue.current = [];
111-
recursivelyFlushAsyncActWork(resolve, reject);
112+
recursivelyFlushAsyncActWork(returnValue, resolve, reject);
113+
} else {
114+
resolve(returnValue);
112115
}
113116
},
114117
};
@@ -117,7 +120,7 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
117120
// immediately resolves. The outer scope will flush the queue.
118121
return {
119122
then(resolve, reject) {
120-
resolve();
123+
resolve(returnValue);
121124
},
122125
};
123126
}
@@ -142,7 +145,7 @@ function popActScope(prevActScopeDepth) {
142145
}
143146
}
144147

145-
function recursivelyFlushAsyncActWork(resolve, reject) {
148+
function recursivelyFlushAsyncActWork(returnValue, resolve, reject) {
146149
if (__DEV__) {
147150
const queue = ReactCurrentActQueue.current;
148151
if (queue !== null) {
@@ -152,17 +155,17 @@ function recursivelyFlushAsyncActWork(resolve, reject) {
152155
if (queue.length === 0) {
153156
// No additional work was scheduled. Finish.
154157
ReactCurrentActQueue.current = null;
155-
resolve();
158+
resolve(returnValue);
156159
} else {
157160
// Keep flushing work until there's none left.
158-
recursivelyFlushAsyncActWork(resolve, reject);
161+
recursivelyFlushAsyncActWork(returnValue, resolve, reject);
159162
}
160163
});
161164
} catch (error) {
162165
reject(error);
163166
}
164167
} else {
165-
resolve();
168+
resolve(returnValue);
166169
}
167170
}
168171
}

0 commit comments

Comments
 (0)