[DevTools] Use use() instead of throwing a Promise in Caches (#34033)
Sebastian Markbåge committed
Jul 29, 2025 at 03:45 UTC
5d7e8b90e29c0f3c995b52d493a0f70dd7d72f3f
19 files changed
+743
-704
packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+51
-90
@@ -857,7 +857,7 @@ describe('Timeline profiler', () => {
857
{
858
"batchUID": 0,
859
"depth": 0,
860
- "duration": 0.014,
860
+ "duration": 0.012,
861
"lanes": "0b0000000000000000000000000000101",
862
"timestamp": 0.008,
863
"type": "render-idle",
@@ -873,25 +873,17 @@ describe('Timeline profiler', () => {
873
{
874
"batchUID": 0,
875
"depth": 0,
876
- "duration": 0.010,
876
+ "duration": 0.008,
877
"lanes": "0b0000000000000000000000000000101",
878
"timestamp": 0.012,
879
"type": "commit",
880
},
881
- {
882
- "batchUID": 0,
883
- "depth": 1,
884
- "duration": 0.001,
885
- "lanes": "0b0000000000000000000000000000101",
886
- "timestamp": 0.02,
887
- "type": "layout-effects",
888
- },
881
{
882
"batchUID": 0,
883
"depth": 0,
884
"duration": 0.004,
885
"lanes": "0b0000000000000000000000000000101",
894
- "timestamp": 0.023,
886
+ "timestamp": 0.021,
887
"type": "passive-effects",
888
},
889
],
@@ -899,9 +891,9 @@ describe('Timeline profiler', () => {
891
{
892
"batchUID": 1,
893
"depth": 0,
902
- "duration": 0.014,
894
+ "duration": 0.012,
895
"lanes": "0b0000000000000000000000000000101",
904
- "timestamp": 0.028,
896
+ "timestamp": 0.026,
897
"type": "render-idle",
898
},
899
{
@@ -909,31 +901,23 @@ describe('Timeline profiler', () => {
901
"depth": 0,
902
"duration": 0.003,
903
"lanes": "0b0000000000000000000000000000101",
912
- "timestamp": 0.028,
904
+ "timestamp": 0.026,
905
"type": "render",
906
},
907
{
908
"batchUID": 1,
909
"depth": 0,
918
- "duration": 0.010,
910
+ "duration": 0.008,
911
"lanes": "0b0000000000000000000000000000101",
920
- "timestamp": 0.032,
912
+ "timestamp": 0.03,
913
"type": "commit",
914
},
923
- {
924
- "batchUID": 1,
925
- "depth": 1,
926
- "duration": 0.001,
927
- "lanes": "0b0000000000000000000000000000101",
928
- "timestamp": 0.04,
929
- "type": "layout-effects",
930
- },
915
{
916
"batchUID": 1,
917
"depth": 0,
918
"duration": 0.003,
919
"lanes": "0b0000000000000000000000000000101",
936
- "timestamp": 0.043,
920
+ "timestamp": 0.039,
921
"type": "passive-effects",
922
},
923
],
@@ -949,26 +933,26 @@ describe('Timeline profiler', () => {
933
{
934
"componentName": "App",
935
"duration": 0.002,
952
- "timestamp": 0.024,
936
+ "timestamp": 0.022,
937
"type": "passive-effect-mount",
938
"warning": null,
939
},
940
{
941
"componentName": "App",
942
"duration": 0.001,
959
- "timestamp": 0.029,
943
+ "timestamp": 0.027,
944
"type": "render",
945
"warning": null,
946
},
947
{
948
"componentName": "App",
949
"duration": 0.001,
966
- "timestamp": 0.044,
950
+ "timestamp": 0.04,
951
"type": "passive-effect-mount",
952
"warning": null,
953
},
954
],
971
- "duration": 0.046,
955
+ "duration": 0.042,
956
"flamechart": [],
957
"internalModuleSourceToRanges": Map {
958
undefined => [
@@ -1031,7 +1015,7 @@ describe('Timeline profiler', () => {
1015
{
1016
"batchUID": 0,
1017
"depth": 0,
1034
- "duration": 0.014,
1018
+ "duration": 0.012,
1019
"lanes": "0b0000000000000000000000000000101",
1020
"timestamp": 0.008,
1021
"type": "render-idle",
@@ -1047,33 +1031,25 @@ describe('Timeline profiler', () => {
1031
{
1032
"batchUID": 0,
1033
"depth": 0,
1050
- "duration": 0.010,
1034
+ "duration": 0.008,
1035
"lanes": "0b0000000000000000000000000000101",
1036
"timestamp": 0.012,
1037
"type": "commit",
1038
},
1055
- {
1056
- "batchUID": 0,
1057
- "depth": 1,
1058
- "duration": 0.001,
1059
- "lanes": "0b0000000000000000000000000000101",
1060
- "timestamp": 0.02,
1061
- "type": "layout-effects",
1062
- },
1039
{
1040
"batchUID": 0,
1041
"depth": 0,
1042
"duration": 0.004,
1043
"lanes": "0b0000000000000000000000000000101",
1068
- "timestamp": 0.023,
1044
+ "timestamp": 0.021,
1045
"type": "passive-effects",
1046
},
1047
{
1048
"batchUID": 1,
1049
"depth": 0,
1074
- "duration": 0.014,
1050
+ "duration": 0.012,
1051
"lanes": "0b0000000000000000000000000000101",
1076
- "timestamp": 0.028,
1052
+ "timestamp": 0.026,
1053
"type": "render-idle",
1054
},
1055
{
@@ -1081,31 +1057,23 @@ describe('Timeline profiler', () => {
1057
"depth": 0,
1058
"duration": 0.003,
1059
"lanes": "0b0000000000000000000000000000101",
1084
- "timestamp": 0.028,
1060
+ "timestamp": 0.026,
1061
"type": "render",
1062
},
1063
{
1064
"batchUID": 1,
1065
"depth": 0,
1090
- "duration": 0.010,
1066
+ "duration": 0.008,
1067
"lanes": "0b0000000000000000000000000000101",
1092
- "timestamp": 0.032,
1068
+ "timestamp": 0.03,
1069
"type": "commit",
1070
},
1095
- {
1096
- "batchUID": 1,
1097
- "depth": 1,
1098
- "duration": 0.001,
1099
- "lanes": "0b0000000000000000000000000000101",
1100
- "timestamp": 0.04,
1101
- "type": "layout-effects",
1102
- },
1071
{
1072
"batchUID": 1,
1073
"depth": 0,
1074
"duration": 0.003,
1075
"lanes": "0b0000000000000000000000000000101",
1108
- "timestamp": 0.043,
1076
+ "timestamp": 0.039,
1077
"type": "passive-effects",
1078
},
1079
],
@@ -1149,7 +1117,7 @@ describe('Timeline profiler', () => {
1117
{
1118
"componentName": "App",
1119
"lanes": "0b0000000000000000000000000000101",
1152
- "timestamp": 0.025,
1120
+ "timestamp": 0.023,
1121
"type": "schedule-state-update",
1122
"warning": null,
1123
},
@@ -1254,6 +1222,15 @@ describe('Timeline profiler', () => {
1222
let promise = null;
1223
let resolvedValue = null;
1224
function readValue(value) {
1225
+ if (React.use) {
1226
+ if (promise === null) {
1227
+ promise = Promise.resolve(true).then(() => {
1228
+ return value;
1229
+ });
1230
+ promise.displayName = 'Testing displayName';
1231
+ }
1232
+ return React.use(promise);
1233
+ }
1234
if (resolvedValue !== null) {
1235
return resolvedValue;
1236
} else if (promise === null) {
@@ -1273,7 +1250,7 @@ describe('Timeline profiler', () => {
1250
const testMarks = [creactCpuProfilerSample()];
1251
1252
const root = ReactDOMClient.createRoot(document.createElement('div'));
1276
- utils.act(() =>
1253
+ await utils.actAsync(() =>
1254
root.render(
1255
<React.Suspense fallback="Loading...">
1256
<Component />
@@ -1823,6 +1800,14 @@ describe('Timeline profiler', () => {
1800
let promise = null;
1801
let resolvedValue = null;
1802
function readValue(value) {
1803
+ if (React.use) {
1804
+ if (promise === null) {
1805
+ promise = Promise.resolve(true).then(() => {
1806
+ return value;
1807
+ });
1808
+ }
1809
+ return React.use(promise);
1810
+ }
1811
if (resolvedValue !== null) {
1812
return resolvedValue;
1813
} else if (promise === null) {
@@ -1881,6 +1866,14 @@ describe('Timeline profiler', () => {
1866
let promise = null;
1867
let resolvedValue = null;
1868
function readValue(value) {
1869
+ if (React.use) {
1870
+ if (promise === null) {
1871
+ promise = Promise.resolve(true).then(() => {
1872
+ return value;
1873
+ });
1874
+ }
1875
+ return React.use(promise);
1876
+ }
1877
if (resolvedValue !== null) {
1878
return resolvedValue;
1879
} else if (promise === null) {
@@ -2192,14 +2185,6 @@ describe('Timeline profiler', () => {
2185
"timestamp": 10,
2186
"type": "commit",
2187
},
2195
- {
2196
- "batchUID": 1,
2197
- "depth": 1,
2198
- "duration": 0,
2199
- "lanes": "0b0000000000000000000000000100000",
2200
- "timestamp": 10,
2201
- "type": "layout-effects",
2202
- },
2188
{
2189
"batchUID": 1,
2190
"depth": 0,
@@ -2234,14 +2219,6 @@ describe('Timeline profiler', () => {
2219
"timestamp": 10,
2220
"type": "commit",
2221
},
2237
- {
2238
- "batchUID": 2,
2239
- "depth": 1,
2240
- "duration": 0,
2241
- "lanes": "0b0000000000000000000000000100000",
2242
- "timestamp": 10,
2243
- "type": "layout-effects",
2244
- },
2222
{
2223
"batchUID": 2,
2224
"depth": 0,
@@ -2292,8 +2269,8 @@ describe('Timeline profiler', () => {
2269
8 => "InputContinuous",
2270
16 => "DefaultHydration",
2271
32 => "Default",
2295
- 64 => "TransitionHydration",
2296
- 128 => "Transition",
2272
+ 64 => undefined,
2273
+ 128 => "TransitionHydration",
2274
256 => "Transition",
2275
512 => "Transition",
2276
1024 => "Transition",
@@ -2349,14 +2326,6 @@ describe('Timeline profiler', () => {
2326
"timestamp": 10,
2327
"type": "commit",
2328
},
2352
- {
2353
- "batchUID": 1,
2354
- "depth": 1,
2355
- "duration": 0,
2356
- "lanes": "0b0000000000000000000000000100000",
2357
- "timestamp": 10,
2358
- "type": "layout-effects",
2359
- },
2329
{
2330
"batchUID": 1,
2331
"depth": 0,
@@ -2389,14 +2358,6 @@ describe('Timeline profiler', () => {
2358
"timestamp": 10,
2359
"type": "commit",
2360
},
2392
- {
2393
- "batchUID": 2,
2394
- "depth": 1,
2395
- "duration": 0,
2396
- "lanes": "0b0000000000000000000000000100000",
2397
- "timestamp": 10,
2398
- "type": "layout-effects",
2399
- },
2361
{
2362
"batchUID": 2,
2363
"depth": 0,
packages/react-devtools-shared/src/__tests__/profilerStore-test.js
+5
-1
@@ -215,7 +215,11 @@ describe('ProfilerStore', () => {
215
it('should not throw while initializing context values for Fibers within a not-yet-mounted subtree', () => {
216
const promise = new Promise(resolve => {});
217
const SuspendingView = () => {
218
- throw promise;
218
+ if (React.use) {
219
+ React.use(promise);
220
+ } else {
221
+ throw promise;
222
+ }
223
};
224
225
const App = () => {
packages/react-devtools-shared/src/__tests__/profilingCache-test.js
+8
@@ -682,6 +682,14 @@ describe('ProfilingCache', () => {
682
it('should calculate durations correctly for suspended views', async () => {
683
let data;
684
const getData = () => {
685
+ if (React.use) {
686
+ if (!data) {
687
+ data = new Promise(resolve => {
688
+ resolve('abc');
689
+ });
690
+ }
691
+ return React.use(data);
692
+ }
693
if (data) {
694
return data;
695
} else {
packages/react-devtools-shared/src/__tests__/store-test.js
+226
-186
@@ -44,10 +44,10 @@ describe('Store', () => {
44
const {render, unmount, createContainer} = getVersionedRenderImplementation();
45
46
// @reactVersion >= 18.0
47
- it('should not allow a root node to be collapsed', () => {
47
+ it('should not allow a root node to be collapsed', async () => {
48
const Component = () => <div>Hi</div>;
49
50
- act(() => render(<Component count={4} />));
50
+ await act(() => render(<Component count={4} />));
51
expect(store).toMatchInlineSnapshot(`
52
[root]
53
<Component>
@@ -63,16 +63,16 @@ describe('Store', () => {
63
});
64
65
// @reactVersion >= 18.0
66
- it('should properly handle a root with no visible nodes', () => {
66
+ it('should properly handle a root with no visible nodes', async () => {
67
const Root = ({children}) => children;
68
69
- act(() => render(<Root>{null}</Root>));
69
+ await act(() => render(<Root>{null}</Root>));
70
expect(store).toMatchInlineSnapshot(`
71
[root]
72
<Root>
73
`);
74
75
- act(() => render(<div />));
75
+ await act(() => render(<div />));
76
expect(store).toMatchInlineSnapshot(`[root]`);
77
});
78
@@ -82,20 +82,24 @@ describe('Store', () => {
82
// I'mnot yet sure of how to reduce the GitHub reported production case to a test though.
83
// See https://github.com/facebook/react/issues/21445
84
// @reactVersion >= 18.0
85
- it('should handle when a component mounts before its owner', () => {
85
+ it('should handle when a component mounts before its owner', async () => {
86
const promise = new Promise(resolve => {});
87
88
let Dynamic = null;
89
const Owner = () => {
90
Dynamic = <Child />;
91
- throw promise;
91
+ if (React.use) {
92
+ React.use(promise);
93
+ } else {
94
+ throw promise;
95
+ }
96
};
97
const Parent = () => {
98
return Dynamic;
99
};
100
const Child = () => null;
101
98
- act(() =>
102
+ await act(() =>
103
render(
104
<>
105
<React.Suspense fallback="Loading...">
@@ -114,11 +118,11 @@ describe('Store', () => {
118
});
119
120
// @reactVersion >= 18.0
117
- it('should handle multibyte character strings', () => {
121
+ it('should handle multibyte character strings', async () => {
122
const Component = () => null;
123
Component.displayName = '🟩💜🔵';
124
121
- act(() => render(<Component />));
125
+ await act(() => render(<Component />));
126
expect(store).toMatchInlineSnapshot(`
127
[root]
128
<🟩💜🔵>
@@ -126,7 +130,7 @@ describe('Store', () => {
130
});
131
132
describe('StrictMode compliance', () => {
129
- it('should mark strict root elements as strict', () => {
133
+ it('should mark strict root elements as strict', async () => {
134
const App = () => <Component />;
135
const Component = () => null;
136
@@ -134,7 +138,7 @@ describe('Store', () => {
138
const root = ReactDOMClient.createRoot(container, {
139
unstable_strictMode: true,
140
});
137
- act(() => {
141
+ await act(() => {
142
root.render(<App />);
143
});
144
@@ -143,13 +147,13 @@ describe('Store', () => {
147
});
148
149
// @reactVersion >= 18.0
146
- it('should mark non strict root elements as not strict', () => {
150
+ it('should mark non strict root elements as not strict', async () => {
151
const App = () => <Component />;
152
const Component = () => null;
153
154
const container = document.createElement('div');
155
const root = ReactDOMClient.createRoot(container);
152
- act(() => {
156
+ await act(() => {
157
root.render(<App />);
158
});
159
@@ -157,7 +161,7 @@ describe('Store', () => {
161
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(true);
162
});
163
160
- it('should mark StrictMode subtree elements as strict', () => {
164
+ it('should mark StrictMode subtree elements as strict', async () => {
165
const App = () => (
166
<React.StrictMode>
167
<Component />
@@ -167,7 +171,7 @@ describe('Store', () => {
171
172
const container = document.createElement('div');
173
const root = ReactDOMClient.createRoot(container);
170
- act(() => {
174
+ await act(() => {
175
root.render(<App />);
176
});
177
@@ -182,7 +186,7 @@ describe('Store', () => {
186
});
187
188
// @reactVersion >= 18.0
185
- it('should support mount and update operations', () => {
189
+ it('should support mount and update operations', async () => {
190
const Grandparent = ({count}) => (
191
<React.Fragment>
192
<Parent count={count} />
@@ -193,7 +197,7 @@ describe('Store', () => {
197
new Array(count).fill(true).map((_, index) => <Child key={index} />);
198
const Child = () => <div>Hi!</div>;
199
196
- act(() => render(<Grandparent count={4} />));
200
+ await act(() => render(<Grandparent count={4} />));
201
expect(store).toMatchInlineSnapshot(`
202
[root]
203
▾ <Grandparent>
@@ -209,7 +213,7 @@ describe('Store', () => {
213
<Child key="3">
214
`);
215
212
- act(() => render(<Grandparent count={2} />));
216
+ await act(() => render(<Grandparent count={2} />));
217
expect(store).toMatchInlineSnapshot(`
218
[root]
219
▾ <Grandparent>
@@ -221,14 +225,14 @@ describe('Store', () => {
225
<Child key="1">
226
`);
227
224
- act(() => unmount());
228
+ await act(() => unmount());
229
expect(store).toMatchInlineSnapshot(``);
230
});
231
232
// @reactVersion >= 18.0
233
// @reactVersion < 19
234
// @gate !disableLegacyMode
231
- it('should support mount and update operations for multiple roots (legacy render)', () => {
235
+ it('should support mount and update operations for multiple roots (legacy render)', async () => {
236
const Parent = ({count}) =>
237
new Array(count).fill(true).map((_, index) => <Child key={index} />);
238
const Child = () => <div>Hi!</div>;
@@ -236,7 +240,7 @@ describe('Store', () => {
240
const containerA = document.createElement('div');
241
const containerB = document.createElement('div');
242
239
- act(() => {
243
+ await act(() => {
244
legacyRender(<Parent key="A" count={3} />, containerA);
245
legacyRender(<Parent key="B" count={2} />, containerB);
246
});
@@ -252,7 +256,7 @@ describe('Store', () => {
256
<Child key="1">
257
`);
258
255
- act(() => {
259
+ await act(() => {
260
legacyRender(<Parent key="A" count={4} />, containerA);
261
legacyRender(<Parent key="B" count={1} />, containerB);
262
});
@@ -268,7 +272,7 @@ describe('Store', () => {
272
<Child key="0">
273
`);
274
271
- act(() => ReactDOM.unmountComponentAtNode(containerB));
275
+ await act(() => ReactDOM.unmountComponentAtNode(containerB));
276
expect(store).toMatchInlineSnapshot(`
277
[root]
278
▾ <Parent key="A">
@@ -278,12 +282,12 @@ describe('Store', () => {
282
<Child key="3">
283
`);
284
281
- act(() => ReactDOM.unmountComponentAtNode(containerA));
285
+ await act(() => ReactDOM.unmountComponentAtNode(containerA));
286
expect(store).toMatchInlineSnapshot(``);
287
});
288
289
// @reactVersion >= 18.0
286
- it('should support mount and update operations for multiple roots (createRoot)', () => {
290
+ it('should support mount and update operations for multiple roots (createRoot)', async () => {
291
const Parent = ({count}) =>
292
new Array(count).fill(true).map((_, index) => <Child key={index} />);
293
const Child = () => <div>Hi!</div>;
@@ -294,7 +298,7 @@ describe('Store', () => {
298
const rootA = ReactDOMClient.createRoot(containerA);
299
const rootB = ReactDOMClient.createRoot(containerB);
300
297
- act(() => {
301
+ await act(() => {
302
rootA.render(<Parent key="A" count={3} />);
303
rootB.render(<Parent key="B" count={2} />);
304
});
@@ -310,7 +314,7 @@ describe('Store', () => {
314
<Child key="1">
315
`);
316
313
- act(() => {
317
+ await act(() => {
318
rootA.render(<Parent key="A" count={4} />);
319
rootB.render(<Parent key="B" count={1} />);
320
});
@@ -326,7 +330,7 @@ describe('Store', () => {
330
<Child key="0">
331
`);
332
329
- act(() => rootB.unmount());
333
+ await act(() => rootB.unmount());
334
expect(store).toMatchInlineSnapshot(`
335
[root]
336
▾ <Parent key="A">
@@ -336,12 +340,12 @@ describe('Store', () => {
340
<Child key="3">
341
`);
342
339
- act(() => rootA.unmount());
343
+ await act(() => rootA.unmount());
344
expect(store).toMatchInlineSnapshot(``);
345
});
346
347
// @reactVersion >= 18.0
344
- it('should filter DOM nodes from the store tree', () => {
348
+ it('should filter DOM nodes from the store tree', async () => {
349
const Grandparent = () => (
350
<div>
351
<div>
@@ -357,7 +361,7 @@ describe('Store', () => {
361
);
362
const Child = () => <div>Hi!</div>;
363
360
- act(() => render(<Grandparent count={4} />));
364
+ await act(() => render(<Grandparent count={4} />));
365
expect(store).toMatchInlineSnapshot(`
366
[root]
367
▾ <Grandparent>
@@ -369,10 +373,14 @@ describe('Store', () => {
373
});
374
375
// @reactVersion >= 18.0
372
- it('should display Suspense nodes properly in various states', () => {
376
+ it('should display Suspense nodes properly in various states', async () => {
377
const Loading = () => <div>Loading...</div>;
378
const SuspendingComponent = () => {
375
- throw new Promise(() => {});
379
+ if (React.use) {
380
+ React.use(new Promise(() => {}));
381
+ } else {
382
+ throw new Promise(() => {});
383
+ }
384
};
385
const Component = () => {
386
return <div>Hello</div>;
@@ -390,7 +398,7 @@ describe('Store', () => {
398
</React.Fragment>
399
);
400
393
- act(() => render(<Wrapper shouldSuspense={true} />));
401
+ await act(() => render(<Wrapper shouldSuspense={true} />));
402
expect(store).toMatchInlineSnapshot(`
403
[root]
404
▾ <Wrapper>
@@ -399,7 +407,7 @@ describe('Store', () => {
407
<Loading>
408
`);
409
402
- act(() => {
410
+ await act(() => {
411
render(<Wrapper shouldSuspense={false} />);
412
});
413
expect(store).toMatchInlineSnapshot(`
@@ -412,11 +420,15 @@ describe('Store', () => {
420
});
421
422
// @reactVersion >= 18.0
415
- it('should support nested Suspense nodes', () => {
423
+ it('should support nested Suspense nodes', async () => {
424
const Component = () => null;
425
const Loading = () => <div>Loading...</div>;
426
const Never = () => {
419
- throw new Promise(() => {});
427
+ if (React.use) {
428
+ React.use(new Promise(() => {}));
429
+ } else {
430
+ throw new Promise(() => {});
431
+ }
432
};
433
434
const Wrapper = ({
@@ -451,7 +463,7 @@ describe('Store', () => {
463
</React.Fragment>
464
);
465
454
- act(() =>
466
+ await act(() =>
467
render(
468
<Wrapper
469
suspendParent={false}
@@ -474,7 +486,7 @@ describe('Store', () => {
486
<Loading key="Suspense 3 Fallback">
487
<Component key="Unrelated at End">
488
`);
477
- act(() =>
489
+ await act(() =>
490
render(
491
<Wrapper
492
suspendParent={false}
@@ -497,7 +509,7 @@ describe('Store', () => {
509
<Loading key="Suspense 3 Fallback">
510
<Component key="Unrelated at End">
511
`);
500
- act(() =>
512
+ await act(() =>
513
render(
514
<Wrapper
515
suspendParent={false}
@@ -520,7 +532,7 @@ describe('Store', () => {
532
<Loading key="Suspense 3 Fallback">
533
<Component key="Unrelated at End">
534
`);
523
- act(() =>
535
+ await act(() =>
536
render(
537
<Wrapper
538
suspendParent={false}
@@ -543,7 +555,7 @@ describe('Store', () => {
555
<Loading key="Suspense 3 Fallback">
556
<Component key="Unrelated at End">
557
`);
546
- act(() =>
558
+ await act(() =>
559
render(
560
<Wrapper
561
suspendParent={true}
@@ -559,7 +571,7 @@ describe('Store', () => {
571
▾ <Suspense>
572
<Loading key="Parent Fallback">
573
`);
562
- act(() =>
574
+ await act(() =>
575
render(
576
<Wrapper
577
suspendParent={false}
@@ -582,7 +594,7 @@ describe('Store', () => {
594
<Loading key="Suspense 3 Fallback">
595
<Component key="Unrelated at End">
596
`);
585
- act(() =>
597
+ await act(() =>
598
render(
599
<Wrapper
600
suspendParent={false}
@@ -607,7 +619,7 @@ describe('Store', () => {
619
`);
620
621
const rendererID = getRendererID();
610
- act(() =>
622
+ await act(() =>
623
agent.overrideSuspense({
624
id: store.getElementIDAtIndex(4),
625
rendererID,
@@ -628,7 +640,7 @@ describe('Store', () => {
640
<Loading key="Suspense 3 Fallback">
641
<Component key="Unrelated at End">
642
`);
631
- act(() =>
643
+ await act(() =>
644
agent.overrideSuspense({
645
id: store.getElementIDAtIndex(2),
646
rendererID,
@@ -642,7 +654,7 @@ describe('Store', () => {
654
▾ <Suspense>
655
<Loading key="Parent Fallback">
656
`);
645
- act(() =>
657
+ await act(() =>
658
render(
659
<Wrapper
660
suspendParent={false}
@@ -658,7 +670,7 @@ describe('Store', () => {
670
▾ <Suspense>
671
<Loading key="Parent Fallback">
672
`);
661
- act(() =>
673
+ await act(() =>
674
agent.overrideSuspense({
675
id: store.getElementIDAtIndex(2),
676
rendererID,
@@ -679,7 +691,7 @@ describe('Store', () => {
691
<Loading key="Suspense 3 Fallback">
692
<Component key="Unrelated at End">
693
`);
682
- act(() =>
694
+ await act(() =>
695
agent.overrideSuspense({
696
id: store.getElementIDAtIndex(4),
697
rendererID,
@@ -700,7 +712,7 @@ describe('Store', () => {
712
<Loading key="Suspense 3 Fallback">
713
<Component key="Unrelated at End">
714
`);
703
- act(() =>
715
+ await act(() =>
716
render(
717
<Wrapper
718
suspendParent={false}
@@ -725,10 +737,14 @@ describe('Store', () => {
737
`);
738
});
739
728
- it('should display a partially rendered SuspenseList', () => {
740
+ it('should display a partially rendered SuspenseList', async () => {
741
const Loading = () => <div>Loading...</div>;
742
const SuspendingComponent = () => {
731
- throw new Promise(() => {});
743
+ if (React.use) {
744
+ React.use(new Promise(() => {}));
745
+ } else {
746
+ throw new Promise(() => {});
747
+ }
748
};
749
const Component = () => {
750
return <div>Hello</div>;
@@ -747,7 +763,7 @@ describe('Store', () => {
763
764
const container = document.createElement('div');
765
const root = ReactDOMClient.createRoot(container);
750
- act(() => {
766
+ await act(() => {
767
root.render(<Wrapper shouldSuspense={true} />);
768
});
769
expect(store).toMatchInlineSnapshot(`
@@ -759,7 +775,7 @@ describe('Store', () => {
775
<Loading>
776
`);
777
762
- act(() => {
778
+ await act(() => {
779
root.render(<Wrapper shouldSuspense={false} />);
780
});
781
expect(store).toMatchInlineSnapshot(`
@@ -774,7 +790,7 @@ describe('Store', () => {
790
});
791
792
// @reactVersion >= 18.0
777
- it('should support collapsing parts of the tree', () => {
793
+ it('should support collapsing parts of the tree', async () => {
794
const Grandparent = ({count}) => (
795
<React.Fragment>
796
<Parent count={count} />
@@ -785,7 +801,7 @@ describe('Store', () => {
801
new Array(count).fill(true).map((_, index) => <Child key={index} />);
802
const Child = () => <div>Hi!</div>;
803
788
- act(() => render(<Grandparent count={2} />));
804
+ await act(() => render(<Grandparent count={2} />));
805
expect(store).toMatchInlineSnapshot(`
806
[root]
807
▾ <Grandparent>
@@ -801,7 +817,7 @@ describe('Store', () => {
817
const parentOneID = store.getElementIDAtIndex(1);
818
const parentTwoID = store.getElementIDAtIndex(4);
819
804
- act(() => store.toggleIsCollapsed(parentOneID, true));
820
+ await act(() => store.toggleIsCollapsed(parentOneID, true));
821
expect(store).toMatchInlineSnapshot(`
822
[root]
823
▾ <Grandparent>
@@ -811,7 +827,7 @@ describe('Store', () => {
827
<Child key="1">
828
`);
829
814
- act(() => store.toggleIsCollapsed(parentTwoID, true));
830
+ await act(() => store.toggleIsCollapsed(parentTwoID, true));
831
expect(store).toMatchInlineSnapshot(`
832
[root]
833
▾ <Grandparent>
@@ -819,7 +835,7 @@ describe('Store', () => {
835
▸ <Parent>
836
`);
837
822
- act(() => store.toggleIsCollapsed(parentOneID, false));
838
+ await act(() => store.toggleIsCollapsed(parentOneID, false));
839
expect(store).toMatchInlineSnapshot(`
840
[root]
841
▾ <Grandparent>
@@ -829,13 +845,13 @@ describe('Store', () => {
845
▸ <Parent>
846
`);
847
832
- act(() => store.toggleIsCollapsed(grandparentID, true));
848
+ await act(() => store.toggleIsCollapsed(grandparentID, true));
849
expect(store).toMatchInlineSnapshot(`
850
[root]
851
▸ <Grandparent>
852
`);
853
838
- act(() => store.toggleIsCollapsed(grandparentID, false));
854
+ await act(() => store.toggleIsCollapsed(grandparentID, false));
855
expect(store).toMatchInlineSnapshot(`
856
[root]
857
▾ <Grandparent>
@@ -847,7 +863,7 @@ describe('Store', () => {
863
});
864
865
// @reactVersion >= 18.0
850
- it('should support reordering of children', () => {
866
+ it('should support reordering of children', async () => {
867
const Root = ({children}) => children;
868
const Component = () => null;
869
@@ -856,7 +872,7 @@ describe('Store', () => {
872
const foo = <Foo key="foo" />;
873
const bar = <Bar key="bar" />;
874
859
- act(() => render(<Root>{[foo, bar]}</Root>));
875
+ await act(() => render(<Root>{[foo, bar]}</Root>));
876
expect(store).toMatchInlineSnapshot(`
877
[root]
878
▾ <Root>
@@ -867,7 +883,7 @@ describe('Store', () => {
883
<Component key="1">
884
`);
885
870
- act(() => render(<Root>{[bar, foo]}</Root>));
886
+ await act(() => render(<Root>{[bar, foo]}</Root>));
887
expect(store).toMatchInlineSnapshot(`
888
[root]
889
▾ <Root>
@@ -878,13 +894,17 @@ describe('Store', () => {
894
<Component key="0">
895
`);
896
881
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));
897
+ await act(() =>
898
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), true),
899
+ );
900
expect(store).toMatchInlineSnapshot(`
901
[root]
902
▸ <Root>
903
`);
904
887
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
905
+ await act(() =>
906
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
907
+ );
908
expect(store).toMatchInlineSnapshot(`
909
[root]
910
▾ <Root>
@@ -903,12 +923,12 @@ describe('Store', () => {
923
});
924
925
// @reactVersion >= 18.0
906
- it('should support mount and update operations', () => {
926
+ it('should support mount and update operations', async () => {
927
const Parent = ({count}) =>
928
new Array(count).fill(true).map((_, index) => <Child key={index} />);
929
const Child = () => <div>Hi!</div>;
930
911
- act(() =>
931
+ await act(() =>
932
render(
933
<React.Fragment>
934
<Parent count={1} />
@@ -922,7 +942,7 @@ describe('Store', () => {
942
▸ <Parent>
943
`);
944
925
- act(() =>
945
+ await act(() =>
946
render(
947
<React.Fragment>
948
<Parent count={2} />
@@ -936,14 +956,14 @@ describe('Store', () => {
956
▸ <Parent>
957
`);
958
939
- act(() => unmount());
959
+ await act(() => unmount());
960
expect(store).toMatchInlineSnapshot(``);
961
});
962
963
// @reactVersion >= 18.0
964
// @reactVersion < 19
965
// @gate !disableLegacyMode
946
- it('should support mount and update operations for multiple roots (legacy render)', () => {
966
+ it('should support mount and update operations for multiple roots (legacy render)', async () => {
967
const Parent = ({count}) =>
968
new Array(count).fill(true).map((_, index) => <Child key={index} />);
969
const Child = () => <div>Hi!</div>;
@@ -951,7 +971,7 @@ describe('Store', () => {
971
const containerA = document.createElement('div');
972
const containerB = document.createElement('div');
973
954
- act(() => {
974
+ await act(() => {
975
legacyRender(<Parent key="A" count={3} />, containerA);
976
legacyRender(<Parent key="B" count={2} />, containerB);
977
});
@@ -962,7 +982,7 @@ describe('Store', () => {
982
▸ <Parent key="B">
983
`);
984
965
- act(() => {
985
+ await act(() => {
986
legacyRender(<Parent key="A" count={4} />, containerA);
987
legacyRender(<Parent key="B" count={1} />, containerB);
988
});
@@ -973,18 +993,18 @@ describe('Store', () => {
993
▸ <Parent key="B">
994
`);
995
976
- act(() => ReactDOM.unmountComponentAtNode(containerB));
996
+ await act(() => ReactDOM.unmountComponentAtNode(containerB));
997
expect(store).toMatchInlineSnapshot(`
998
[root]
999
▸ <Parent key="A">
1000
`);
1001
982
- act(() => ReactDOM.unmountComponentAtNode(containerA));
1002
+ await act(() => ReactDOM.unmountComponentAtNode(containerA));
1003
expect(store).toMatchInlineSnapshot(``);
1004
});
1005
1006
// @reactVersion >= 18.0
987
- it('should support mount and update operations for multiple roots (createRoot)', () => {
1007
+ it('should support mount and update operations for multiple roots (createRoot)', async () => {
1008
const Parent = ({count}) =>
1009
new Array(count).fill(true).map((_, index) => <Child key={index} />);
1010
const Child = () => <div>Hi!</div>;
@@ -995,7 +1015,7 @@ describe('Store', () => {
1015
const rootA = ReactDOMClient.createRoot(containerA);
1016
const rootB = ReactDOMClient.createRoot(containerB);
1017
998
- act(() => {
1018
+ await act(() => {
1019
rootA.render(<Parent key="A" count={3} />);
1020
rootB.render(<Parent key="B" count={2} />);
1021
});
@@ -1006,7 +1026,7 @@ describe('Store', () => {
1026
▸ <Parent key="B">
1027
`);
1028
1009
- act(() => {
1029
+ await act(() => {
1030
rootA.render(<Parent key="A" count={4} />);
1031
rootB.render(<Parent key="B" count={1} />);
1032
});
@@ -1017,18 +1037,18 @@ describe('Store', () => {
1037
▸ <Parent key="B">
1038
`);
1039
1020
- act(() => rootB.unmount());
1040
+ await act(() => rootB.unmount());
1041
expect(store).toMatchInlineSnapshot(`
1042
[root]
1043
▸ <Parent key="A">
1044
`);
1045
1026
- act(() => rootA.unmount());
1046
+ await act(() => rootA.unmount());
1047
expect(store).toMatchInlineSnapshot(``);
1048
});
1049
1050
// @reactVersion >= 18.0
1031
- it('should filter DOM nodes from the store tree', () => {
1051
+ it('should filter DOM nodes from the store tree', async () => {
1052
const Grandparent = () => (
1053
<div>
1054
<div>
@@ -1044,13 +1064,15 @@ describe('Store', () => {
1064
);
1065
const Child = () => <div>Hi!</div>;
1066
1047
- act(() => render(<Grandparent count={4} />));
1067
+ await act(() => render(<Grandparent count={4} />));
1068
expect(store).toMatchInlineSnapshot(`
1069
[root]
1070
▸ <Grandparent>
1071
`);
1072
1053
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
1073
+ await act(() =>
1074
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
1075
+ );
1076
expect(store).toMatchInlineSnapshot(`
1077
[root]
1078
▾ <Grandparent>
@@ -1058,7 +1080,9 @@ describe('Store', () => {
1080
▸ <Parent>
1081
`);
1082
1061
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
1083
+ await act(() =>
1084
+ store.toggleIsCollapsed(store.getElementIDAtIndex(1), false),
1085
+ );
1086
expect(store).toMatchInlineSnapshot(`
1087
[root]
1088
▾ <Grandparent>
@@ -1069,10 +1093,14 @@ describe('Store', () => {
1093
});
1094
1095
// @reactVersion >= 18.0
1072
- it('should display Suspense nodes properly in various states', () => {
1096
+ it('should display Suspense nodes properly in various states', async () => {
1097
const Loading = () => <div>Loading...</div>;
1098
const SuspendingComponent = () => {
1075
- throw new Promise(() => {});
1099
+ if (React.use) {
1100
+ React.use(new Promise(() => {}));
1101
+ } else {
1102
+ throw new Promise(() => {});
1103
+ }
1104
};
1105
const Component = () => {
1106
return <div>Hello</div>;
@@ -1090,15 +1118,19 @@ describe('Store', () => {
1118
</React.Fragment>
1119
);
1120
1093
- act(() => render(<Wrapper shouldSuspense={true} />));
1121
+ await act(() => render(<Wrapper shouldSuspense={true} />));
1122
expect(store).toMatchInlineSnapshot(`
1123
[root]
1124
▸ <Wrapper>
1125
`);
1126
1127
// This test isn't meaningful unless we expand the suspended tree
1100
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
1101
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(2), false));
1128
+ await act(() =>
1129
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
1130
+ );
1131
+ await act(() =>
1132
+ store.toggleIsCollapsed(store.getElementIDAtIndex(2), false),
1133
+ );
1134
expect(store).toMatchInlineSnapshot(`
1135
[root]
1136
▾ <Wrapper>
@@ -1107,7 +1139,7 @@ describe('Store', () => {
1139
<Loading>
1140
`);
1141
1110
- act(() => {
1142
+ await act(() => {
1143
render(<Wrapper shouldSuspense={false} />);
1144
});
1145
expect(store).toMatchInlineSnapshot(`
@@ -1120,7 +1152,7 @@ describe('Store', () => {
1152
});
1153
1154
// @reactVersion >= 18.0
1123
- it('should support expanding parts of the tree', () => {
1155
+ it('should support expanding parts of the tree', async () => {
1156
const Grandparent = ({count}) => (
1157
<React.Fragment>
1158
<Parent count={count} />
@@ -1131,7 +1163,7 @@ describe('Store', () => {
1163
new Array(count).fill(true).map((_, index) => <Child key={index} />);
1164
const Child = () => <div>Hi!</div>;
1165
1134
- act(() => render(<Grandparent count={2} />));
1166
+ await act(() => render(<Grandparent count={2} />));
1167
expect(store).toMatchInlineSnapshot(`
1168
[root]
1169
▸ <Grandparent>
@@ -1139,7 +1171,7 @@ describe('Store', () => {
1171
1172
const grandparentID = store.getElementIDAtIndex(0);
1173
1142
- act(() => store.toggleIsCollapsed(grandparentID, false));
1174
+ await act(() => store.toggleIsCollapsed(grandparentID, false));
1175
expect(store).toMatchInlineSnapshot(`
1176
[root]
1177
▾ <Grandparent>
@@ -1150,7 +1182,7 @@ describe('Store', () => {
1182
const parentOneID = store.getElementIDAtIndex(1);
1183
const parentTwoID = store.getElementIDAtIndex(2);
1184
1153
- act(() => store.toggleIsCollapsed(parentOneID, false));
1185
+ await act(() => store.toggleIsCollapsed(parentOneID, false));
1186
expect(store).toMatchInlineSnapshot(`
1187
[root]
1188
▾ <Grandparent>
@@ -1160,7 +1192,7 @@ describe('Store', () => {
1192
▸ <Parent>
1193
`);
1194
1163
- act(() => store.toggleIsCollapsed(parentTwoID, false));
1195
+ await act(() => store.toggleIsCollapsed(parentTwoID, false));
1196
expect(store).toMatchInlineSnapshot(`
1197
[root]
1198
▾ <Grandparent>
@@ -1172,7 +1204,7 @@ describe('Store', () => {
1204
<Child key="1">
1205
`);
1206
1175
- act(() => store.toggleIsCollapsed(parentOneID, true));
1207
+ await act(() => store.toggleIsCollapsed(parentOneID, true));
1208
expect(store).toMatchInlineSnapshot(`
1209
[root]
1210
▾ <Grandparent>
@@ -1182,7 +1214,7 @@ describe('Store', () => {
1214
<Child key="1">
1215
`);
1216
1185
- act(() => store.toggleIsCollapsed(parentTwoID, true));
1217
+ await act(() => store.toggleIsCollapsed(parentTwoID, true));
1218
expect(store).toMatchInlineSnapshot(`
1219
[root]
1220
▾ <Grandparent>
@@ -1190,7 +1222,7 @@ describe('Store', () => {
1222
▸ <Parent>
1223
`);
1224
1193
- act(() => store.toggleIsCollapsed(grandparentID, true));
1225
+ await act(() => store.toggleIsCollapsed(grandparentID, true));
1226
expect(store).toMatchInlineSnapshot(`
1227
[root]
1228
▸ <Grandparent>
@@ -1198,7 +1230,7 @@ describe('Store', () => {
1230
});
1231
1232
// @reactVersion >= 18.0
1201
- it('should support expanding deep parts of the tree', () => {
1233
+ it('should support expanding deep parts of the tree', async () => {
1234
const Wrapper = ({forwardedRef}) => (
1235
<Nested depth={3} forwardedRef={forwardedRef} />
1236
);
@@ -1211,7 +1243,7 @@ describe('Store', () => {
1243
1244
const ref = React.createRef();
1245
1214
- act(() => render(<Wrapper forwardedRef={ref} />));
1246
+ await act(() => render(<Wrapper forwardedRef={ref} />));
1247
expect(store).toMatchInlineSnapshot(`
1248
[root]
1249
▸ <Wrapper>
@@ -1219,7 +1251,7 @@ describe('Store', () => {
1251
1252
const deepestedNodeID = agent.getIDForHostInstance(ref.current);
1253
1222
- act(() => store.toggleIsCollapsed(deepestedNodeID, false));
1254
+ await act(() => store.toggleIsCollapsed(deepestedNodeID, false));
1255
expect(store).toMatchInlineSnapshot(`
1256
[root]
1257
▾ <Wrapper>
@@ -1231,13 +1263,13 @@ describe('Store', () => {
1263
1264
const rootID = store.getElementIDAtIndex(0);
1265
1234
- act(() => store.toggleIsCollapsed(rootID, true));
1266
+ await act(() => store.toggleIsCollapsed(rootID, true));
1267
expect(store).toMatchInlineSnapshot(`
1268
[root]
1269
▸ <Wrapper>
1270
`);
1271
1240
- act(() => store.toggleIsCollapsed(rootID, false));
1272
+ await act(() => store.toggleIsCollapsed(rootID, false));
1273
expect(store).toMatchInlineSnapshot(`
1274
[root]
1275
▾ <Wrapper>
@@ -1249,14 +1281,14 @@ describe('Store', () => {
1281
1282
const id = store.getElementIDAtIndex(1);
1283
1252
- act(() => store.toggleIsCollapsed(id, true));
1284
+ await act(() => store.toggleIsCollapsed(id, true));
1285
expect(store).toMatchInlineSnapshot(`
1286
[root]
1287
▾ <Wrapper>
1288
▸ <Nested>
1289
`);
1290
1259
- act(() => store.toggleIsCollapsed(id, false));
1291
+ await act(() => store.toggleIsCollapsed(id, false));
1292
expect(store).toMatchInlineSnapshot(`
1293
[root]
1294
▾ <Wrapper>
@@ -1268,7 +1300,7 @@ describe('Store', () => {
1300
});
1301
1302
// @reactVersion >= 18.0
1271
- it('should support reordering of children', () => {
1303
+ it('should support reordering of children', async () => {
1304
const Root = ({children}) => children;
1305
const Component = () => null;
1306
@@ -1277,19 +1309,21 @@ describe('Store', () => {
1309
const foo = <Foo key="foo" />;
1310
const bar = <Bar key="bar" />;
1311
1280
- act(() => render(<Root>{[foo, bar]}</Root>));
1312
+ await act(() => render(<Root>{[foo, bar]}</Root>));
1313
expect(store).toMatchInlineSnapshot(`
1314
[root]
1315
▸ <Root>
1316
`);
1317
1286
- act(() => render(<Root>{[bar, foo]}</Root>));
1318
+ await act(() => render(<Root>{[bar, foo]}</Root>));
1319
expect(store).toMatchInlineSnapshot(`
1320
[root]
1321
▸ <Root>
1322
`);
1323
1292
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
1324
+ await act(() =>
1325
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
1326
+ );
1327
expect(store).toMatchInlineSnapshot(`
1328
[root]
1329
▾ <Root>
@@ -1297,7 +1331,7 @@ describe('Store', () => {
1331
▸ <Foo key="foo">
1332
`);
1333
1300
- act(() => {
1334
+ await act(() => {
1335
store.toggleIsCollapsed(store.getElementIDAtIndex(2), false);
1336
store.toggleIsCollapsed(store.getElementIDAtIndex(1), false);
1337
});
@@ -1311,7 +1345,9 @@ describe('Store', () => {
1345
<Component key="0">
1346
`);
1347
1314
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));
1348
+ await act(() =>
1349
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), true),
1350
+ );
1351
expect(store).toMatchInlineSnapshot(`
1352
[root]
1353
▸ <Root>
@@ -1319,7 +1355,7 @@ describe('Store', () => {
1355
});
1356
1357
// @reactVersion >= 18.0
1322
- it('should not add new nodes when suspense is toggled', () => {
1358
+ it('should not add new nodes when suspense is toggled', async () => {
1359
const SuspenseTree = () => {
1360
return (
1361
<React.Suspense fallback={<Fallback>Loading outer</Fallback>}>
@@ -1332,14 +1368,18 @@ describe('Store', () => {
1368
const Parent = () => <Child />;
1369
const Child = () => null;
1370
1335
- act(() => render(<SuspenseTree />));
1371
+ await act(() => render(<SuspenseTree />));
1372
expect(store).toMatchInlineSnapshot(`
1373
[root]
1374
▸ <SuspenseTree>
1375
`);
1376
1341
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
1342
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
1377
+ await act(() =>
1378
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
1379
+ );
1380
+ await act(() =>
1381
+ store.toggleIsCollapsed(store.getElementIDAtIndex(1), false),
1382
+ );
1383
expect(store).toMatchInlineSnapshot(`
1384
[root]
1385
▾ <SuspenseTree>
@@ -1350,7 +1390,7 @@ describe('Store', () => {
1390
const rendererID = getRendererID();
1391
const suspenseID = store.getElementIDAtIndex(1);
1392
1353
- act(() =>
1393
+ await act(() =>
1394
agent.overrideSuspense({
1395
id: suspenseID,
1396
rendererID,
@@ -1364,7 +1404,7 @@ describe('Store', () => {
1404
<Fallback>
1405
`);
1406
1367
- act(() =>
1407
+ await act(() =>
1408
agent.overrideSuspense({
1409
id: suspenseID,
1410
rendererID,
@@ -1386,7 +1426,7 @@ describe('Store', () => {
1426
});
1427
1428
// @reactVersion >= 18.0
1389
- it('should support a single root with a single child', () => {
1429
+ it('should support a single root with a single child', async () => {
1430
const Grandparent = () => (
1431
<React.Fragment>
1432
<Parent />
@@ -1396,7 +1436,7 @@ describe('Store', () => {
1436
const Parent = () => <Child />;
1437
const Child = () => null;
1438
1399
- act(() => render(<Grandparent />));
1439
+ await act(() => render(<Grandparent />));
1440
1441
for (let i = 0; i < store.numElements; i++) {
1442
expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
@@ -1404,12 +1444,12 @@ describe('Store', () => {
1444
});
1445
1446
// @reactVersion >= 18.0
1407
- it('should support multiple roots with one children each', () => {
1447
+ it('should support multiple roots with one children each', async () => {
1448
const Grandparent = () => <Parent />;
1449
const Parent = () => <Child />;
1450
const Child = () => null;
1451
1412
- act(() => {
1452
+ await act(() => {
1453
render(<Grandparent />);
1454
render(<Grandparent />);
1455
});
@@ -1420,12 +1460,12 @@ describe('Store', () => {
1460
});
1461
1462
// @reactVersion >= 18.0
1423
- it('should support a single root with multiple top level children', () => {
1463
+ it('should support a single root with multiple top level children', async () => {
1464
const Grandparent = () => <Parent />;
1465
const Parent = () => <Child />;
1466
const Child = () => null;
1467
1428
- act(() =>
1468
+ await act(() =>
1469
render(
1470
<React.Fragment>
1471
<Grandparent />
@@ -1440,12 +1480,12 @@ describe('Store', () => {
1480
});
1481
1482
// @reactVersion >= 18.0
1443
- it('should support multiple roots with multiple top level children', () => {
1483
+ it('should support multiple roots with multiple top level children', async () => {
1484
const Grandparent = () => <Parent />;
1485
const Parent = () => <Child />;
1486
const Child = () => null;
1487
1448
- act(() => {
1488
+ await act(() => {
1489
render(
1490
<React.Fragment>
1491
<Grandparent />
@@ -1472,7 +1512,7 @@ describe('Store', () => {
1512
// @reactVersion >= 18.0
1513
// @reactVersion < 19
1514
// @gate !disableLegacyMode
1475
- it('detects and updates profiling support based on the attached roots (legacy render)', () => {
1515
+ it('detects and updates profiling support based on the attached roots (legacy render)', async () => {
1516
const Component = () => null;
1517
1518
const containerA = document.createElement('div');
@@ -1480,19 +1520,19 @@ describe('Store', () => {
1520
1521
expect(store.rootSupportsBasicProfiling).toBe(false);
1522
1483
- act(() => legacyRender(<Component />, containerA));
1523
+ await act(() => legacyRender(<Component />, containerA));
1524
expect(store.rootSupportsBasicProfiling).toBe(true);
1525
1486
- act(() => legacyRender(<Component />, containerB));
1487
- act(() => ReactDOM.unmountComponentAtNode(containerA));
1526
+ await act(() => legacyRender(<Component />, containerB));
1527
+ await act(() => ReactDOM.unmountComponentAtNode(containerA));
1528
expect(store.rootSupportsBasicProfiling).toBe(true);
1529
1490
- act(() => ReactDOM.unmountComponentAtNode(containerB));
1530
+ await act(() => ReactDOM.unmountComponentAtNode(containerB));
1531
expect(store.rootSupportsBasicProfiling).toBe(false);
1532
});
1533
1534
// @reactVersion >= 18
1495
- it('detects and updates profiling support based on the attached roots (createRoot)', () => {
1535
+ it('detects and updates profiling support based on the attached roots (createRoot)', async () => {
1536
const Component = () => null;
1537
1538
const containerA = document.createElement('div');
@@ -1503,26 +1543,26 @@ describe('Store', () => {
1543
1544
expect(store.rootSupportsBasicProfiling).toBe(false);
1545
1506
- act(() => rootA.render(<Component />));
1546
+ await act(() => rootA.render(<Component />));
1547
expect(store.rootSupportsBasicProfiling).toBe(true);
1548
1509
- act(() => rootB.render(<Component />));
1510
- act(() => rootA.unmount());
1549
+ await act(() => rootB.render(<Component />));
1550
+ await act(() => rootA.unmount());
1551
expect(store.rootSupportsBasicProfiling).toBe(true);
1552
1513
- act(() => rootB.unmount());
1553
+ await act(() => rootB.unmount());
1554
expect(store.rootSupportsBasicProfiling).toBe(false);
1555
});
1556
1557
// @reactVersion >= 18.0
1518
- it('should properly serialize non-string key values', () => {
1558
+ it('should properly serialize non-string key values', async () => {
1559
const Child = () => null;
1560
1561
// Bypass React element's automatic stringifying of keys intentionally.
1562
// This is pretty hacky.
1563
const fauxElement = Object.assign({}, <Child />, {key: 123});
1564
1525
- act(() => render([fauxElement]));
1565
+ await act(() => render([fauxElement]));
1566
expect(store).toMatchInlineSnapshot(`
1567
[root]
1568
<Child key="123">
@@ -1581,12 +1621,12 @@ describe('Store', () => {
1621
);
1622
1623
// Render once to start fetching the lazy component
1584
- act(() => render(<App />));
1624
+ await act(() => render(<App />));
1625
1626
await Promise.resolve();
1627
1628
// Render again after it resolves
1589
- act(() => render(<App />));
1629
+ await act(() => render(<App />));
1630
1631
expect(store).toMatchInlineSnapshot(`
1632
[root]
@@ -1638,7 +1678,7 @@ describe('Store', () => {
1678
const container = document.createElement('div');
1679
1680
// Render once to start fetching the lazy component
1641
- act(() => legacyRender(<App renderChildren={true} />, container));
1681
+ await act(() => legacyRender(<App renderChildren={true} />, container));
1682
1683
expect(store).toMatchInlineSnapshot(`
1684
[root]
@@ -1649,7 +1689,7 @@ describe('Store', () => {
1689
await Promise.resolve();
1690
1691
// Render again after it resolves
1652
- act(() => legacyRender(<App renderChildren={true} />, container));
1692
+ await act(() => legacyRender(<App renderChildren={true} />, container));
1693
1694
expect(store).toMatchInlineSnapshot(`
1695
[root]
@@ -1659,7 +1699,7 @@ describe('Store', () => {
1699
`);
1700
1701
// Render again to unmount it
1662
- act(() => legacyRender(<App renderChildren={false} />, container));
1702
+ await act(() => legacyRender(<App renderChildren={false} />, container));
1703
1704
expect(store).toMatchInlineSnapshot(`
1705
[root]
@@ -1673,7 +1713,7 @@ describe('Store', () => {
1713
const root = ReactDOMClient.createRoot(container);
1714
1715
// Render once to start fetching the lazy component
1676
- act(() => root.render(<App renderChildren={true} />));
1716
+ await act(() => root.render(<App renderChildren={true} />));
1717
1718
expect(store).toMatchInlineSnapshot(`
1719
[root]
@@ -1684,7 +1724,7 @@ describe('Store', () => {
1724
await Promise.resolve();
1725
1726
// Render again after it resolves
1687
- act(() => root.render(<App renderChildren={true} />));
1727
+ await act(() => root.render(<App renderChildren={true} />));
1728
1729
expect(store).toMatchInlineSnapshot(`
1730
[root]
@@ -1694,7 +1734,7 @@ describe('Store', () => {
1734
`);
1735
1736
// Render again to unmount it
1697
- act(() => root.render(<App renderChildren={false} />));
1737
+ await act(() => root.render(<App renderChildren={false} />));
1738
1739
expect(store).toMatchInlineSnapshot(`
1740
[root]
@@ -1709,7 +1749,7 @@ describe('Store', () => {
1749
const container = document.createElement('div');
1750
1751
// Render once to start fetching the lazy component
1712
- act(() => legacyRender(<App renderChildren={true} />, container));
1752
+ await act(() => legacyRender(<App renderChildren={true} />, container));
1753
1754
expect(store).toMatchInlineSnapshot(`
1755
[root]
@@ -1718,7 +1758,7 @@ describe('Store', () => {
1758
`);
1759
1760
// Render again to unmount it before it finishes loading
1721
- act(() => legacyRender(<App renderChildren={false} />, container));
1761
+ await act(() => legacyRender(<App renderChildren={false} />, container));
1762
1763
expect(store).toMatchInlineSnapshot(`
1764
[root]
@@ -1733,7 +1773,7 @@ describe('Store', () => {
1773
const root = ReactDOMClient.createRoot(container);
1774
1775
// Render once to start fetching the lazy component
1736
- act(() => root.render(<App renderChildren={true} />));
1776
+ await act(() => root.render(<App renderChildren={true} />));
1777
1778
expect(store).toMatchInlineSnapshot(`
1779
[root]
@@ -1742,7 +1782,7 @@ describe('Store', () => {
1782
`);
1783
1784
// Render again to unmount it before it finishes loading
1745
- act(() => root.render(<App renderChildren={false} />));
1785
+ await act(() => root.render(<App renderChildren={false} />));
1786
1787
expect(store).toMatchInlineSnapshot(`
1788
[root]
@@ -1753,15 +1793,15 @@ describe('Store', () => {
1793
1794
describe('inline errors and warnings', () => {
1795
// @reactVersion >= 18.0
1756
- it('during render are counted', () => {
1796
+ it('during render are counted', async () => {
1797
function Example() {
1798
console.error('test-only: render error');
1799
console.warn('test-only: render warning');
1800
return null;
1801
}
1802
1763
- withErrorsOrWarningsIgnored(['test-only:'], () => {
1764
- act(() => render(<Example />));
1803
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
1804
+ await act(() => render(<Example />));
1805
});
1806
1807
expect(store).toMatchInlineSnapshot(`
@@ -1770,8 +1810,8 @@ describe('Store', () => {
1810
<Example> ✕⚠
1811
`);
1812
1773
- withErrorsOrWarningsIgnored(['test-only:'], () => {
1774
- act(() => render(<Example rerender={1} />));
1813
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
1814
+ await act(() => render(<Example rerender={1} />));
1815
});
1816
1817
expect(store).toMatchInlineSnapshot(`
@@ -1782,7 +1822,7 @@ describe('Store', () => {
1822
});
1823
1824
// @reactVersion >= 18.0
1785
- it('during layout get counted', () => {
1825
+ it('during layout get counted', async () => {
1826
function Example() {
1827
React.useLayoutEffect(() => {
1828
console.error('test-only: layout error');
@@ -1791,8 +1831,8 @@ describe('Store', () => {
1831
return null;
1832
}
1833
1794
- withErrorsOrWarningsIgnored(['test-only:'], () => {
1795
- act(() => render(<Example />));
1834
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
1835
+ await act(() => render(<Example />));
1836
});
1837
1838
expect(store).toMatchInlineSnapshot(`
@@ -1801,8 +1841,8 @@ describe('Store', () => {
1841
<Example> ✕⚠
1842
`);
1843
1804
- withErrorsOrWarningsIgnored(['test-only:'], () => {
1805
- act(() => render(<Example rerender={1} />));
1844
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
1845
+ await act(() => render(<Example rerender={1} />));
1846
});
1847
1848
expect(store).toMatchInlineSnapshot(`
@@ -1818,7 +1858,7 @@ describe('Store', () => {
1858
}
1859
1860
// @reactVersion >= 18.0
1821
- it('are counted (after no delay)', () => {
1861
+ it('are counted (after no delay)', async () => {
1862
function Example() {
1863
React.useEffect(() => {
1864
console.error('test-only: passive error');
@@ -1827,8 +1867,8 @@ describe('Store', () => {
1867
return null;
1868
}
1869
1830
- withErrorsOrWarningsIgnored(['test-only:'], () => {
1831
- act(() => {
1870
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
1871
+ await act(() => {
1872
render(<Example />);
1873
}, false);
1874
});
@@ -1839,12 +1879,12 @@ describe('Store', () => {
1879
<Example> ✕⚠
1880
`);
1881
1842
- act(() => unmount());
1882
+ await act(() => unmount());
1883
expect(store).toMatchInlineSnapshot(``);
1884
});
1885
1886
// @reactVersion >= 18.0
1847
- it('are flushed early when there is a new commit', () => {
1887
+ it('are flushed early when there is a new commit', async () => {
1888
function Example() {
1889
React.useEffect(() => {
1890
console.error('test-only: passive error');
@@ -1890,7 +1930,7 @@ describe('Store', () => {
1930
`);
1931
});
1932
1893
- act(() => unmount());
1933
+ await act(() => unmount());
1934
expect(store).toMatchInlineSnapshot(``);
1935
});
1936
});
@@ -1898,7 +1938,7 @@ describe('Store', () => {
1938
// In React 19, JSX warnings were moved into the renderer - https://github.com/facebook/react/pull/29088
1939
// The warning is moved to the Child instead of the Parent.
1940
// @reactVersion >= 19.0.1
1901
- it('from react get counted [React >= 19.0.1]', () => {
1941
+ it('from react get counted [React >= 19.0.1]', async () => {
1942
function Example() {
1943
return [<Child />];
1944
}
@@ -1923,7 +1963,7 @@ describe('Store', () => {
1963
1964
// @reactVersion >= 18.0
1965
// @reactVersion < 19.0
1926
- it('from react get counted [React 18.x]', () => {
1966
+ it('from react get counted [React 18.x]', async () => {
1967
function Example() {
1968
return [<Child />];
1969
}
@@ -1947,15 +1987,15 @@ describe('Store', () => {
1987
});
1988
1989
// @reactVersion >= 18.0
1950
- it('can be cleared for the whole app', () => {
1990
+ it('can be cleared for the whole app', async () => {
1991
function Example() {
1992
console.error('test-only: render error');
1993
console.warn('test-only: render warning');
1994
return null;
1995
}
1996
1957
- withErrorsOrWarningsIgnored(['test-only:'], () => {
1958
- act(() =>
1997
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
1998
+ await act(() =>
1999
render(
2000
<React.Fragment>
2001
<Example />
@@ -1988,15 +2028,15 @@ describe('Store', () => {
2028
});
2029
2030
// @reactVersion >= 18.0
1991
- it('can be cleared for particular Fiber (only warnings)', () => {
2031
+ it('can be cleared for particular Fiber (only warnings)', async () => {
2032
function Example() {
2033
console.error('test-only: render error');
2034
console.warn('test-only: render warning');
2035
return null;
2036
}
2037
1998
- withErrorsOrWarningsIgnored(['test-only:'], () => {
1999
- act(() =>
2038
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
2039
+ await act(() =>
2040
render(
2041
<React.Fragment>
2042
<Example />
@@ -2033,15 +2073,15 @@ describe('Store', () => {
2073
});
2074
2075
// @reactVersion >= 18.0
2036
- it('can be cleared for a particular Fiber (only errors)', () => {
2076
+ it('can be cleared for a particular Fiber (only errors)', async () => {
2077
function Example() {
2078
console.error('test-only: render error');
2079
console.warn('test-only: render warning');
2080
return null;
2081
}
2082
2043
- withErrorsOrWarningsIgnored(['test-only:'], () => {
2044
- act(() =>
2083
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
2084
+ await act(() =>
2085
render(
2086
<React.Fragment>
2087
<Example />
@@ -2078,7 +2118,7 @@ describe('Store', () => {
2118
});
2119
2120
// @reactVersion >= 18.0
2081
- it('are updated when fibers are removed from the tree', () => {
2121
+ it('are updated when fibers are removed from the tree', async () => {
2122
function ComponentWithWarning() {
2123
console.warn('test-only: render warning');
2124
return null;
@@ -2093,8 +2133,8 @@ describe('Store', () => {
2133
return null;
2134
}
2135
2096
- withErrorsOrWarningsIgnored(['test-only:'], () => {
2097
- act(() =>
2136
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
2137
+ await act(() =>
2138
render(
2139
<React.Fragment>
2140
<ComponentWithError />
@@ -2112,8 +2152,8 @@ describe('Store', () => {
2152
<ComponentWithWarningAndError> ✕⚠
2153
`);
2154
2115
- withErrorsOrWarningsIgnored(['test-only:'], () => {
2116
- act(() =>
2155
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
2156
+ await act(() =>
2157
render(
2158
<React.Fragment>
2159
<ComponentWithWarning />
@@ -2129,8 +2169,8 @@ describe('Store', () => {
2169
<ComponentWithWarningAndError> ✕⚠
2170
`);
2171
2132
- withErrorsOrWarningsIgnored(['test-only:'], () => {
2133
- act(() =>
2172
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
2173
+ await act(() =>
2174
render(
2175
<React.Fragment>
2176
<ComponentWithWarning />
@@ -2144,8 +2184,8 @@ describe('Store', () => {
2184
<ComponentWithWarning> ⚠
2185
`);
2186
2147
- withErrorsOrWarningsIgnored(['test-only:'], () => {
2148
- act(() => render(<React.Fragment />));
2187
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
2188
+ await act(() => render(<React.Fragment />));
2189
});
2190
expect(store).toMatchInlineSnapshot(`[root]`);
2191
expect(store.componentWithErrorCount).toBe(0);
packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
+5
-1
@@ -509,7 +509,11 @@ describe('Store component filters', () => {
509
510
const Component = ({shouldSuspend}) => {
511
if (shouldSuspend) {
512
- throw promise;
512
+ if (React.use) {
513
+ React.use(promise);
514
+ } else {
515
+ throw promise;
516
+ }
517
}
518
return null;
519
};
packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
+10
-2
@@ -522,7 +522,11 @@ describe('StoreStress (Legacy Mode)', () => {
522
];
523
524
const Never = () => {
525
- throw new Promise(() => {});
525
+ if (React.use) {
526
+ React.use(new Promise(() => {}));
527
+ } else {
528
+ throw new Promise(() => {});
529
+ }
530
};
531
532
const Root = ({children}) => {
@@ -1144,7 +1148,11 @@ describe('StoreStress (Legacy Mode)', () => {
1148
];
1149
1150
const Never = () => {
1147
- throw new Promise(() => {});
1151
+ if (React.use) {
1152
+ React.use(new Promise(() => {}));
1153
+ } else {
1154
+ throw new Promise(() => {});
1155
+ }
1156
};
1157
1158
const MaybeSuspend = ({children, suspend}) => {
packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
+78
-70
@@ -38,7 +38,7 @@ describe('StoreStressConcurrent', () => {
38
// This is a stress test for the tree mount/update/unmount traversal.
39
// It renders different trees that should produce the same output.
40
// @reactVersion >= 18.0
41
- it('should handle a stress test with different tree operations (Concurrent Mode)', () => {
41
+ it('should handle a stress test with different tree operations (Concurrent Mode)', async () => {
42
let setShowX;
43
const A = () => 'a';
44
const B = () => 'b';
@@ -151,26 +151,26 @@ describe('StoreStressConcurrent', () => {
151
root = ReactDOMClient.createRoot(container);
152
153
// Verify mounting 'abcde'.
154
- act(() => root.render(<Parent>{cases[i]}</Parent>));
154
+ await act(() => root.render(<Parent>{cases[i]}</Parent>));
155
expect(container.textContent).toMatch('abcde');
156
expect(print(store)).toEqual(snapshotForABCDE);
157
158
// Verify switching to 'abxde'.
159
- act(() => {
159
+ await act(() => {
160
setShowX(true);
161
});
162
expect(container.textContent).toMatch('abxde');
163
expect(print(store)).toBe(snapshotForABXDE);
164
165
// Verify switching back to 'abcde'.
166
- act(() => {
166
+ await act(() => {
167
setShowX(false);
168
});
169
expect(container.textContent).toMatch('abcde');
170
expect(print(store)).toBe(snapshotForABCDE);
171
172
// Clean up.
173
- act(() => root.unmount());
173
+ await act(() => root.unmount());
174
expect(print(store)).toBe('');
175
}
176
@@ -180,19 +180,19 @@ describe('StoreStressConcurrent', () => {
180
root = ReactDOMClient.createRoot(container);
181
for (let i = 0; i < cases.length; i++) {
182
// Verify mounting 'abcde'.
183
- act(() => root.render(<Parent>{cases[i]}</Parent>));
183
+ await act(() => root.render(<Parent>{cases[i]}</Parent>));
184
expect(container.textContent).toMatch('abcde');
185
expect(print(store)).toEqual(snapshotForABCDE);
186
187
// Verify switching to 'abxde'.
188
- act(() => {
188
+ await act(() => {
189
setShowX(true);
190
});
191
expect(container.textContent).toMatch('abxde');
192
expect(print(store)).toBe(snapshotForABXDE);
193
194
// Verify switching back to 'abcde'.
195
- act(() => {
195
+ await act(() => {
196
setShowX(false);
197
});
198
expect(container.textContent).toMatch('abcde');
@@ -204,7 +204,7 @@ describe('StoreStressConcurrent', () => {
204
});
205
206
// @reactVersion >= 18.0
207
- it('should handle stress test with reordering (Concurrent Mode)', () => {
207
+ it('should handle stress test with reordering (Concurrent Mode)', async () => {
208
const A = () => 'a';
209
const B = () => 'b';
210
const C = () => 'c';
@@ -245,10 +245,10 @@ describe('StoreStressConcurrent', () => {
245
let container = document.createElement('div');
246
for (let i = 0; i < steps.length; i++) {
247
const root = ReactDOMClient.createRoot(container);
248
- act(() => root.render(<Root>{steps[i]}</Root>));
248
+ await act(() => root.render(<Root>{steps[i]}</Root>));
249
// We snapshot each step once so it doesn't regress.
250
snapshots.push(print(store));
251
- act(() => root.unmount());
251
+ await act(() => root.unmount());
252
expect(print(store)).toBe('');
253
}
254
@@ -316,13 +316,13 @@ describe('StoreStressConcurrent', () => {
316
for (let j = 0; j < steps.length; j++) {
317
container = document.createElement('div');
318
const root = ReactDOMClient.createRoot(container);
319
- act(() => root.render(<Root>{steps[i]}</Root>));
319
+ await act(() => root.render(<Root>{steps[i]}</Root>));
320
expect(print(store)).toMatch(snapshots[i]);
321
- act(() => root.render(<Root>{steps[j]}</Root>));
321
+ await act(() => root.render(<Root>{steps[j]}</Root>));
322
expect(print(store)).toMatch(snapshots[j]);
323
- act(() => root.render(<Root>{steps[i]}</Root>));
323
+ await act(() => root.render(<Root>{steps[i]}</Root>));
324
expect(print(store)).toMatch(snapshots[i]);
325
- act(() => root.unmount());
325
+ await act(() => root.unmount());
326
expect(print(store)).toBe('');
327
}
328
}
@@ -332,7 +332,7 @@ describe('StoreStressConcurrent', () => {
332
for (let j = 0; j < steps.length; j++) {
333
container = document.createElement('div');
334
const root = ReactDOMClient.createRoot(container);
335
- act(() =>
335
+ await act(() =>
336
root.render(
337
<Root>
338
<div>{steps[i]}</div>
@@ -340,7 +340,7 @@ describe('StoreStressConcurrent', () => {
340
),
341
);
342
expect(print(store)).toMatch(snapshots[i]);
343
- act(() =>
343
+ await act(() =>
344
root.render(
345
<Root>
346
<div>{steps[j]}</div>
@@ -348,7 +348,7 @@ describe('StoreStressConcurrent', () => {
348
),
349
);
350
expect(print(store)).toMatch(snapshots[j]);
351
- act(() =>
351
+ await act(() =>
352
root.render(
353
<Root>
354
<div>{steps[i]}</div>
@@ -356,7 +356,7 @@ describe('StoreStressConcurrent', () => {
356
),
357
);
358
expect(print(store)).toMatch(snapshots[i]);
359
- act(() => root.unmount());
359
+ await act(() => root.unmount());
360
expect(print(store)).toBe('');
361
}
362
}
@@ -392,7 +392,11 @@ describe('StoreStressConcurrent', () => {
392
];
393
394
const Never = () => {
395
- throw new Promise(() => {});
395
+ if (React.use) {
396
+ React.use(new Promise(() => {}));
397
+ } else {
398
+ throw new Promise(() => {});
399
+ }
400
};
401
402
const Root = ({children}) => {
@@ -405,7 +409,7 @@ describe('StoreStressConcurrent', () => {
409
let container = document.createElement('div');
410
for (let i = 0; i < steps.length; i++) {
411
const root = ReactDOMClient.createRoot(container);
408
- act(() =>
412
+ await act(() =>
413
root.render(
414
<Root>
415
<X />
@@ -416,7 +420,7 @@ describe('StoreStressConcurrent', () => {
420
);
421
// We snapshot each step once so it doesn't regress.d
422
snapshots.push(print(store));
419
- act(() => root.unmount());
423
+ await act(() => root.unmount());
424
expect(print(store)).toBe('');
425
}
426
@@ -507,7 +511,7 @@ describe('StoreStressConcurrent', () => {
511
// 2. Verify check Suspense can render same steps as initial fallback content.
512
for (let i = 0; i < steps.length; i++) {
513
const root = ReactDOMClient.createRoot(container);
510
- act(() =>
514
+ await act(() =>
515
root.render(
516
<Root>
517
<X />
@@ -521,7 +525,7 @@ describe('StoreStressConcurrent', () => {
525
),
526
);
527
expect(print(store)).toEqual(snapshots[i]);
524
- act(() => root.unmount());
528
+ await act(() => root.unmount());
529
expect(print(store)).toBe('');
530
}
531
@@ -531,7 +535,7 @@ describe('StoreStressConcurrent', () => {
535
// Always start with a fresh container and steps[i].
536
container = document.createElement('div');
537
const root = ReactDOMClient.createRoot(container);
534
- act(() =>
538
+ await act(() =>
539
root.render(
540
<Root>
541
<X />
@@ -542,7 +546,7 @@ describe('StoreStressConcurrent', () => {
546
);
547
expect(print(store)).toEqual(snapshots[i]);
548
// Re-render with steps[j].
545
- act(() =>
549
+ await act(() =>
550
root.render(
551
<Root>
552
<X />
@@ -554,7 +558,7 @@ describe('StoreStressConcurrent', () => {
558
// Verify the successful transition to steps[j].
559
expect(print(store)).toEqual(snapshots[j]);
560
// Check that we can transition back again.
557
- act(() =>
561
+ await act(() =>
562
root.render(
563
<Root>
564
<X />
@@ -565,7 +569,7 @@ describe('StoreStressConcurrent', () => {
569
);
570
expect(print(store)).toEqual(snapshots[i]);
571
// Clean up after every iteration.
568
- act(() => root.unmount());
572
+ await act(() => root.unmount());
573
expect(print(store)).toBe('');
574
}
575
}
@@ -576,7 +580,7 @@ describe('StoreStressConcurrent', () => {
580
// Always start with a fresh container and steps[i].
581
container = document.createElement('div');
582
const root = ReactDOMClient.createRoot(container);
579
- act(() =>
583
+ await act(() =>
584
root.render(
585
<Root>
586
<X />
@@ -591,7 +595,7 @@ describe('StoreStressConcurrent', () => {
595
);
596
expect(print(store)).toEqual(snapshots[i]);
597
// Re-render with steps[j].
594
- act(() =>
598
+ await act(() =>
599
root.render(
600
<Root>
601
<X />
@@ -607,7 +611,7 @@ describe('StoreStressConcurrent', () => {
611
// Verify the successful transition to steps[j].
612
expect(print(store)).toEqual(snapshots[j]);
613
// Check that we can transition back again.
610
- act(() =>
614
+ await act(() =>
615
root.render(
616
<Root>
617
<X />
@@ -622,7 +626,7 @@ describe('StoreStressConcurrent', () => {
626
);
627
expect(print(store)).toEqual(snapshots[i]);
628
// Clean up after every iteration.
625
- act(() => root.unmount());
629
+ await act(() => root.unmount());
630
expect(print(store)).toBe('');
631
}
632
}
@@ -633,7 +637,7 @@ describe('StoreStressConcurrent', () => {
637
// Always start with a fresh container and steps[i].
638
container = document.createElement('div');
639
const root = ReactDOMClient.createRoot(container);
636
- act(() =>
640
+ await act(() =>
641
root.render(
642
<Root>
643
<X />
@@ -644,7 +648,7 @@ describe('StoreStressConcurrent', () => {
648
);
649
expect(print(store)).toEqual(snapshots[i]);
650
// Re-render with steps[j].
647
- act(() =>
651
+ await act(() =>
652
root.render(
653
<Root>
654
<X />
@@ -660,7 +664,7 @@ describe('StoreStressConcurrent', () => {
664
// Verify the successful transition to steps[j].
665
expect(print(store)).toEqual(snapshots[j]);
666
// Check that we can transition back again.
663
- act(() =>
667
+ await act(() =>
668
root.render(
669
<Root>
670
<X />
@@ -671,7 +675,7 @@ describe('StoreStressConcurrent', () => {
675
);
676
expect(print(store)).toEqual(snapshots[i]);
677
// Clean up after every iteration.
674
- act(() => root.unmount());
678
+ await act(() => root.unmount());
679
expect(print(store)).toBe('');
680
}
681
}
@@ -682,7 +686,7 @@ describe('StoreStressConcurrent', () => {
686
// Always start with a fresh container and steps[i].
687
container = document.createElement('div');
688
const root = ReactDOMClient.createRoot(container);
685
- act(() =>
689
+ await act(() =>
690
root.render(
691
<Root>
692
<X />
@@ -697,7 +701,7 @@ describe('StoreStressConcurrent', () => {
701
);
702
expect(print(store)).toEqual(snapshots[i]);
703
// Re-render with steps[j].
700
- act(() =>
704
+ await act(() =>
705
root.render(
706
<Root>
707
<X />
@@ -709,7 +713,7 @@ describe('StoreStressConcurrent', () => {
713
// Verify the successful transition to steps[j].
714
expect(print(store)).toEqual(snapshots[j]);
715
// Check that we can transition back again.
712
- act(() =>
716
+ await act(() =>
717
root.render(
718
<Root>
719
<X />
@@ -724,7 +728,7 @@ describe('StoreStressConcurrent', () => {
728
);
729
expect(print(store)).toEqual(snapshots[i]);
730
// Clean up after every iteration.
727
- act(() => root.unmount());
731
+ await act(() => root.unmount());
732
expect(print(store)).toBe('');
733
}
734
}
@@ -735,7 +739,7 @@ describe('StoreStressConcurrent', () => {
739
// Always start with a fresh container and steps[i].
740
container = document.createElement('div');
741
const root = ReactDOMClient.createRoot(container);
738
- act(() =>
742
+ await act(() =>
743
root.render(
744
<Root>
745
<X />
@@ -772,7 +776,7 @@ describe('StoreStressConcurrent', () => {
776
expect(print(store)).toEqual(snapshots[i]);
777
778
// Trigger actual fallback.
775
- act(() =>
779
+ await act(() =>
780
root.render(
781
<Root>
782
<X />
@@ -788,7 +792,7 @@ describe('StoreStressConcurrent', () => {
792
expect(print(store)).toEqual(snapshots[j]);
793
794
// Force fallback while we're in fallback mode.
791
- act(() => {
795
+ await act(() => {
796
bridge.send('overrideSuspense', {
797
id: suspenseID,
798
rendererID: store.getRendererIDForElement(suspenseID),
@@ -799,7 +803,7 @@ describe('StoreStressConcurrent', () => {
803
expect(print(store)).toEqual(snapshots[j]);
804
805
// Switch to primary mode.
802
- act(() =>
806
+ await act(() =>
807
root.render(
808
<Root>
809
<X />
@@ -859,7 +863,11 @@ describe('StoreStressConcurrent', () => {
863
];
864
865
const Never = () => {
862
- throw new Promise(() => {});
866
+ if (React.use) {
867
+ React.use(new Promise(() => {}));
868
+ } else {
869
+ throw new Promise(() => {});
870
+ }
871
};
872
873
const MaybeSuspend = ({children, suspend}) => {
@@ -890,7 +898,7 @@ describe('StoreStressConcurrent', () => {
898
let container = document.createElement('div');
899
for (let i = 0; i < steps.length; i++) {
900
const root = ReactDOMClient.createRoot(container);
893
- act(() =>
901
+ await act(() =>
902
root.render(
903
<Root>
904
<X />
@@ -903,7 +911,7 @@ describe('StoreStressConcurrent', () => {
911
);
912
// We snapshot each step once so it doesn't regress.
913
snapshots.push(print(store));
906
- act(() => root.unmount());
914
+ await act(() => root.unmount());
915
expect(print(store)).toBe('');
916
}
917
@@ -913,7 +921,7 @@ describe('StoreStressConcurrent', () => {
921
const fallbackSnapshots = [];
922
for (let i = 0; i < steps.length; i++) {
923
const root = ReactDOMClient.createRoot(container);
916
- act(() =>
924
+ await act(() =>
925
root.render(
926
<Root>
927
<X />
@@ -928,7 +936,7 @@ describe('StoreStressConcurrent', () => {
936
);
937
// We snapshot each step once so it doesn't regress.
938
fallbackSnapshots.push(print(store));
931
- act(() => root.unmount());
939
+ await act(() => root.unmount());
940
expect(print(store)).toBe('');
941
}
942
@@ -1046,7 +1054,7 @@ describe('StoreStressConcurrent', () => {
1054
// Always start with a fresh container and steps[i].
1055
container = document.createElement('div');
1056
const root = ReactDOMClient.createRoot(container);
1049
- act(() =>
1057
+ await act(() =>
1058
root.render(
1059
<Root>
1060
<X />
@@ -1059,7 +1067,7 @@ describe('StoreStressConcurrent', () => {
1067
);
1068
expect(print(store)).toEqual(snapshots[i]);
1069
// Re-render with steps[j].
1062
- act(() =>
1070
+ await act(() =>
1071
root.render(
1072
<Root>
1073
<X />
@@ -1073,7 +1081,7 @@ describe('StoreStressConcurrent', () => {
1081
// Verify the successful transition to steps[j].
1082
expect(print(store)).toEqual(snapshots[j]);
1083
// Check that we can transition back again.
1076
- act(() =>
1084
+ await act(() =>
1085
root.render(
1086
<Root>
1087
<X />
@@ -1086,7 +1094,7 @@ describe('StoreStressConcurrent', () => {
1094
);
1095
expect(print(store)).toEqual(snapshots[i]);
1096
// Clean up after every iteration.
1089
- act(() => root.unmount());
1097
+ await act(() => root.unmount());
1098
expect(print(store)).toBe('');
1099
}
1100
}
@@ -1097,7 +1105,7 @@ describe('StoreStressConcurrent', () => {
1105
// Always start with a fresh container and steps[i].
1106
container = document.createElement('div');
1107
const root = ReactDOMClient.createRoot(container);
1100
- act(() =>
1108
+ await act(() =>
1109
root.render(
1110
<Root>
1111
<X />
@@ -1115,7 +1123,7 @@ describe('StoreStressConcurrent', () => {
1123
);
1124
expect(print(store)).toEqual(fallbackSnapshots[i]);
1125
// Re-render with steps[j].
1118
- act(() =>
1126
+ await act(() =>
1127
root.render(
1128
<Root>
1129
<X />
@@ -1134,7 +1142,7 @@ describe('StoreStressConcurrent', () => {
1142
// Verify the successful transition to steps[j].
1143
expect(print(store)).toEqual(fallbackSnapshots[j]);
1144
// Check that we can transition back again.
1137
- act(() =>
1145
+ await act(() =>
1146
root.render(
1147
<Root>
1148
<X />
@@ -1152,7 +1160,7 @@ describe('StoreStressConcurrent', () => {
1160
);
1161
expect(print(store)).toEqual(fallbackSnapshots[i]);
1162
// Clean up after every iteration.
1155
- act(() => root.unmount());
1163
+ await act(() => root.unmount());
1164
expect(print(store)).toBe('');
1165
}
1166
}
@@ -1163,7 +1171,7 @@ describe('StoreStressConcurrent', () => {
1171
// Always start with a fresh container and steps[i].
1172
container = document.createElement('div');
1173
const root = ReactDOMClient.createRoot(container);
1166
- act(() =>
1174
+ await act(() =>
1175
root.render(
1176
<Root>
1177
<X />
@@ -1176,7 +1184,7 @@ describe('StoreStressConcurrent', () => {
1184
);
1185
expect(print(store)).toEqual(snapshots[i]);
1186
// Re-render with steps[j].
1179
- act(() =>
1187
+ await act(() =>
1188
root.render(
1189
<Root>
1190
<X />
@@ -1190,7 +1198,7 @@ describe('StoreStressConcurrent', () => {
1198
// Verify the successful transition to steps[j].
1199
expect(print(store)).toEqual(fallbackSnapshots[j]);
1200
// Check that we can transition back again.
1193
- act(() =>
1201
+ await act(() =>
1202
root.render(
1203
<Root>
1204
<X />
@@ -1203,7 +1211,7 @@ describe('StoreStressConcurrent', () => {
1211
);
1212
expect(print(store)).toEqual(snapshots[i]);
1213
// Clean up after every iteration.
1206
- act(() => root.unmount());
1214
+ await act(() => root.unmount());
1215
expect(print(store)).toBe('');
1216
}
1217
}
@@ -1214,7 +1222,7 @@ describe('StoreStressConcurrent', () => {
1222
// Always start with a fresh container and steps[i].
1223
container = document.createElement('div');
1224
const root = ReactDOMClient.createRoot(container);
1217
- act(() =>
1225
+ await act(() =>
1226
root.render(
1227
<Root>
1228
<X />
@@ -1227,7 +1235,7 @@ describe('StoreStressConcurrent', () => {
1235
);
1236
expect(print(store)).toEqual(fallbackSnapshots[i]);
1237
// Re-render with steps[j].
1230
- act(() =>
1238
+ await act(() =>
1239
root.render(
1240
<Root>
1241
<X />
@@ -1241,7 +1249,7 @@ describe('StoreStressConcurrent', () => {
1249
// Verify the successful transition to steps[j].
1250
expect(print(store)).toEqual(snapshots[j]);
1251
// Check that we can transition back again.
1244
- act(() =>
1252
+ await act(() =>
1253
root.render(
1254
<Root>
1255
<X />
@@ -1254,7 +1262,7 @@ describe('StoreStressConcurrent', () => {
1262
);
1263
expect(print(store)).toEqual(fallbackSnapshots[i]);
1264
// Clean up after every iteration.
1257
- act(() => root.unmount());
1265
+ await act(() => root.unmount());
1266
expect(print(store)).toBe('');
1267
}
1268
}
@@ -1265,7 +1273,7 @@ describe('StoreStressConcurrent', () => {
1273
// Always start with a fresh container and steps[i].
1274
container = document.createElement('div');
1275
const root = ReactDOMClient.createRoot(container);
1268
- act(() =>
1276
+ await act(() =>
1277
root.render(
1278
<Root>
1279
<X />
@@ -1304,7 +1312,7 @@ describe('StoreStressConcurrent', () => {
1312
expect(print(store)).toEqual(snapshots[i]);
1313
1314
// Trigger actual fallback.
1307
- act(() =>
1315
+ await act(() =>
1316
root.render(
1317
<Root>
1318
<X />
@@ -1318,7 +1326,7 @@ describe('StoreStressConcurrent', () => {
1326
expect(print(store)).toEqual(fallbackSnapshots[j]);
1327
1328
// Force fallback while we're in fallback mode.
1321
- act(() => {
1329
+ await act(() => {
1330
bridge.send('overrideSuspense', {
1331
id: suspenseID,
1332
rendererID: store.getRendererIDForElement(suspenseID),
@@ -1329,7 +1337,7 @@ describe('StoreStressConcurrent', () => {
1337
expect(print(store)).toEqual(fallbackSnapshots[j]);
1338
1339
// Switch to primary mode.
1332
- act(() =>
1340
+ await act(() =>
1341
root.render(
1342
<Root>
1343
<X />
@@ -1355,7 +1363,7 @@ describe('StoreStressConcurrent', () => {
1363
expect(print(store)).toEqual(snapshots[i]);
1364
1365
// Clean up after every iteration.
1358
- act(() => root.unmount());
1366
+ await act(() => root.unmount());
1367
expect(print(store)).toBe('');
1368
}
1369
}
packages/react-devtools-shared/src/devtools/cache.js
+29
-57
@@ -7,7 +7,12 @@
7
* @flow
8
*/
9
10
-import type {ReactContext, Thenable} from 'shared/ReactTypes';
10
+import type {
11
+ ReactContext,
12
+ Thenable,
13
+ FulfilledThenable,
14
+ RejectedThenable,
15
+} from 'shared/ReactTypes';
16
17
import * as React from 'react';
18
import {createContext} from 'react';
@@ -26,27 +31,6 @@ import {createContext} from 'react';
31
32
export type {Thenable};
33
29
-interface Suspender {
30
- then(resolve: () => mixed, reject: () => mixed): mixed;
31
-}
32
-
33
-type PendingResult = {
34
- status: 0,
35
- value: Suspender,
36
-};
37
-
38
-type ResolvedResult<Value> = {
39
- status: 1,
40
- value: Value,
41
-};
42
-
43
-type RejectedResult = {
44
- status: 2,
45
- value: mixed,
46
-};
47
-
48
-type Result<Value> = PendingResult | ResolvedResult<Value> | RejectedResult;
49
-
34
export type Resource<Input, Key, Value> = {
35
clear(): void,
36
invalidate(Key): void,
@@ -55,10 +39,6 @@ export type Resource<Input, Key, Value> = {
39
write(Key, Value): void,
40
};
41
58
-const Pending = 0;
59
-const Resolved = 1;
60
-const Rejected = 2;
61
-
42
let readContext;
43
if (typeof React.use === 'function') {
44
readContext = function (Context: ReactContext<null>) {
@@ -115,33 +95,25 @@ function accessResult<Input, Key, Value>(
95
fetch: Input => Thenable<Value>,
96
input: Input,
97
key: Key,
118
-): Result<Value> {
98
+): Thenable<Value> {
99
const entriesForResource = getEntriesForResource(resource);
100
const entry = entriesForResource.get(key);
101
if (entry === undefined) {
102
const thenable = fetch(input);
103
thenable.then(
104
value => {
125
- if (newResult.status === Pending) {
126
- const resolvedResult: ResolvedResult<Value> = (newResult: any);
127
- resolvedResult.status = Resolved;
128
- resolvedResult.value = value;
129
- }
105
+ const fulfilledThenable: FulfilledThenable<Value> = (thenable: any);
106
+ fulfilledThenable.status = 'fulfilled';
107
+ fulfilledThenable.value = value;
108
},
109
error => {
132
- if (newResult.status === Pending) {
133
- const rejectedResult: RejectedResult = (newResult: any);
134
- rejectedResult.status = Rejected;
135
- rejectedResult.value = error;
136
- }
110
+ const rejectedThenable: RejectedThenable<Value> = (thenable: any);
111
+ rejectedThenable.status = 'rejected';
112
+ rejectedThenable.reason = error;
113
},
114
);
139
- const newResult: PendingResult = {
140
- status: Pending,
141
- value: thenable,
142
- };
143
- entriesForResource.set(key, newResult);
144
- return newResult;
115
+ entriesForResource.set(key, thenable);
116
+ return thenable;
117
} else {
118
return entry;
119
}
@@ -167,23 +139,22 @@ export function createResource<Input, Key, Value>(
139
readContext(CacheContext);
140
141
const key = hashInput(input);
170
- const result: Result<Value> = accessResult(resource, fetch, input, key);
142
+ const result: Thenable<Value> = accessResult(resource, fetch, input, key);
143
+ if (typeof React.use === 'function') {
144
+ return React.use(result);
145
+ }
146
+
147
switch (result.status) {
172
- case Pending: {
173
- const suspender = result.value;
174
- throw suspender;
175
- }
176
- case Resolved: {
148
+ case 'fulfilled': {
149
const value = result.value;
150
return value;
151
}
180
- case Rejected: {
181
- const error = result.value;
152
+ case 'rejected': {
153
+ const error = result.reason;
154
throw error;
155
}
156
default:
185
- // Should be unreachable
186
- return (undefined: any);
157
+ throw result;
158
}
159
},
160
@@ -198,12 +169,13 @@ export function createResource<Input, Key, Value>(
169
write(key: Key, value: Value): void {
170
const entriesForResource = getEntriesForResource(resource);
171
201
- const resolvedResult: ResolvedResult<Value> = {
202
- status: Resolved,
172
+ const fulfilledThenable: FulfilledThenable<Value> = (Promise.resolve(
173
value,
204
- };
174
+ ): any);
175
+ fulfilledThenable.status = 'fulfilled';
176
+ fulfilledThenable.value = value;
177
206
- entriesForResource.set(key, resolvedResult);
178
+ entriesForResource.set(key, fulfilledThenable);
179
},
180
};
181
packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js
+1
-1
@@ -20,7 +20,7 @@ import {getMetaValueLabel, serializeHooksForCopy} from '../utils';
20
import Store from '../../store';
21
import styles from './InspectedElementHooksTree.css';
22
import {meta} from '../../../hydration';
23
-import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
23
+import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookSourceLocation';
24
import HookNamesModuleLoaderContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext';
25
import isArray from 'react-devtools-shared/src/isArray';
26
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js
+61
-61
@@ -7,54 +7,46 @@
7
* @flow
8
*/
9
10
-import type {Wakeable} from 'shared/ReactTypes';
10
+import type {
11
+ Thenable,
12
+ FulfilledThenable,
13
+ RejectedThenable,
14
+} from 'shared/ReactTypes';
15
import type {GitHubIssue} from './githubAPI';
16
17
+import * as React from 'react';
18
+
19
import {unstable_getCacheForType as getCacheForType} from 'react';
20
import {searchGitHubIssues} from './githubAPI';
21
22
const API_TIMEOUT = 3000;
17
-
18
-const Pending = 0;
19
-const Resolved = 1;
20
-const Rejected = 2;
21
-
22
-type PendingRecord = {
23
- status: 0,
24
- value: Wakeable,
25
-};
26
-
27
-type ResolvedRecord<T> = {
28
- status: 1,
29
- value: T,
30
-};
31
-
32
-type RejectedRecord = {
33
- status: 2,
34
- value: null,
35
-};
36
-
37
-type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
38
-
39
-function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
40
- if (record.status === Resolved) {
41
- // This is just a type refinement.
42
- return record;
43
- } else if (record.status === Rejected) {
44
- // This is just a type refinement.
45
- return record;
23
+function readRecord<T>(record: Thenable<T>): T | null {
24
+ if (typeof React.use === 'function') {
25
+ try {
26
+ return React.use(record);
27
+ } catch (x) {
28
+ if (x === null) {
29
+ return null;
30
+ }
31
+ throw x;
32
+ }
33
+ }
34
+ if (record.status === 'fulfilled') {
35
+ return record.value;
36
+ } else if (record.status === 'rejected') {
37
+ return null;
38
} else {
47
- throw record.value;
39
+ throw record;
40
}
41
}
42
51
-type GitHubIssueMap = Map<string, Record<GitHubIssue>>;
43
+type GitHubIssueMap = Map<string, Thenable<GitHubIssue>>;
44
45
function createMap(): GitHubIssueMap {
46
return new Map();
47
}
48
57
-function getRecordMap(): Map<string, Record<GitHubIssue>> {
49
+function getRecordMap(): Map<string, Thenable<GitHubIssue>> {
50
return getCacheForType(createMap);
51
}
52
@@ -65,10 +57,15 @@ export function findGitHubIssue(errorMessage: string): GitHubIssue | null {
57
let record = map.get(errorMessage);
58
59
if (!record) {
68
- const callbacks = new Set<() => mixed>();
69
- const wakeable: Wakeable = {
70
- then(callback: () => mixed) {
60
+ const callbacks = new Set<(value: any) => mixed>();
61
+ const rejectCallbacks = new Set<(reason: mixed) => mixed>();
62
+ const thenable: Thenable<GitHubIssue> = {
63
+ status: 'pending',
64
+ value: null,
65
+ reason: null,
66
+ then(callback: (value: any) => mixed, reject: (error: mixed) => mixed) {
67
callbacks.add(callback);
68
+ rejectCallbacks.add(reject);
69
},
70
71
// Optional property used by Timeline:
@@ -76,13 +73,17 @@ export function findGitHubIssue(errorMessage: string): GitHubIssue | null {
73
};
74
const wake = () => {
75
// This assumes they won't throw.
79
- callbacks.forEach(callback => callback());
76
+ callbacks.forEach(callback => callback((thenable: any).value));
77
callbacks.clear();
78
+ rejectCallbacks.clear();
79
};
82
- const newRecord: Record<GitHubIssue> = (record = {
83
- status: Pending,
84
- value: wakeable,
85
- });
80
+ const wakeRejections = () => {
81
+ // This assumes they won't throw.
82
+ rejectCallbacks.forEach(callback => callback((thenable: any).reason));
83
+ rejectCallbacks.clear();
84
+ callbacks.clear();
85
+ };
86
+ record = thenable;
87
88
let didTimeout = false;
89
@@ -93,41 +94,40 @@ export function findGitHubIssue(errorMessage: string): GitHubIssue | null {
94
}
95
96
if (maybeItem) {
96
- const resolvedRecord =
97
- ((newRecord: any): ResolvedRecord<GitHubIssue>);
98
- resolvedRecord.status = Resolved;
99
- resolvedRecord.value = maybeItem;
97
+ const fulfilledThenable: FulfilledThenable<GitHubIssue> =
98
+ (thenable: any);
99
+ fulfilledThenable.status = 'fulfilled';
100
+ fulfilledThenable.value = maybeItem;
101
+ wake();
102
} else {
101
- const notFoundRecord = ((newRecord: any): RejectedRecord);
102
- notFoundRecord.status = Rejected;
103
- notFoundRecord.value = null;
103
+ const notFoundThenable: RejectedThenable<GitHubIssue> =
104
+ (thenable: any);
105
+ notFoundThenable.status = 'rejected';
106
+ notFoundThenable.reason = null;
107
+ wakeRejections();
108
}
105
-
106
- wake();
109
})
110
.catch(error => {
109
- const thrownRecord = ((newRecord: any): RejectedRecord);
110
- thrownRecord.status = Rejected;
111
- thrownRecord.value = null;
112
-
113
- wake();
111
+ const rejectedThenable: RejectedThenable<GitHubIssue> = (thenable: any);
112
+ rejectedThenable.status = 'rejected';
113
+ rejectedThenable.reason = null;
114
+ wakeRejections();
115
});
116
117
// Only wait a little while for GitHub results before showing a fallback.
118
setTimeout(() => {
119
didTimeout = true;
120
120
- const timedoutRecord = ((newRecord: any): RejectedRecord);
121
- timedoutRecord.status = Rejected;
122
- timedoutRecord.value = null;
123
-
124
- wake();
121
+ const timedoutThenable: RejectedThenable<GitHubIssue> = (thenable: any);
122
+ timedoutThenable.status = 'rejected';
123
+ timedoutThenable.reason = null;
124
+ wakeRejections();
125
}, API_TIMEOUT);
126
127
map.set(errorMessage, record);
128
}
129
130
- const response = readRecord(record).value;
130
+ const response = readRecord(record);
131
return response;
132
}
133
packages/react-devtools-shared/src/devtools/views/Profiler/HookChangeSummary.js
+2
-4
@@ -21,10 +21,8 @@ import ButtonIcon from '../ButtonIcon';
21
import {InspectedElementContext} from '../Components/InspectedElementContext';
22
import {StoreContext} from '../context';
23
24
-import {
25
- getAlreadyLoadedHookNames,
26
- getHookSourceLocationKey,
27
-} from 'react-devtools-shared/src/hookNamesCache';
24
+import {getAlreadyLoadedHookNames} from 'react-devtools-shared/src/hookNamesCache';
25
+import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookSourceLocation';
26
import Toggle from '../Toggle';
27
import type {HooksNode} from 'react-debug-tools/src/ReactDebugHooks';
28
import type {ChangeDescription} from './types';
packages/react-devtools-shared/src/dynamicImportCache.js
+56
-50
@@ -9,30 +9,15 @@
9
10
import {__DEBUG__} from 'react-devtools-shared/src/constants';
11
12
-import type {Thenable, Wakeable} from 'shared/ReactTypes';
12
+import type {
13
+ Thenable,
14
+ FulfilledThenable,
15
+ RejectedThenable,
16
+} from 'shared/ReactTypes';
17
14
-const TIMEOUT = 30000;
15
-
16
-const Pending = 0;
17
-const Resolved = 1;
18
-const Rejected = 2;
19
-
20
-type PendingRecord = {
21
- status: 0,
22
- value: Wakeable,
23
-};
24
-
25
-type ResolvedRecord<T> = {
26
- status: 1,
27
- value: T,
28
-};
29
-
30
-type RejectedRecord = {
31
- status: 2,
32
- value: null,
33
-};
18
+import * as React from 'react';
19
35
-type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
20
+const TIMEOUT = 30000;
21
22
type Module = any;
23
type ModuleLoaderFunction = () => Thenable<Module>;
@@ -42,16 +27,23 @@ type ModuleLoaderFunction = () => Thenable<Module>;
27
// Modules are static anyway.
28
const moduleLoaderFunctionToModuleMap: Map<ModuleLoaderFunction, Module> =
29
new Map();
45
-
46
-function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
47
- if (record.status === Resolved) {
48
- // This is just a type refinement.
49
- return record;
50
- } else if (record.status === Rejected) {
51
- // This is just a type refinement.
52
- return record;
30
+function readRecord<T>(record: Thenable<T>): T | null {
31
+ if (typeof React.use === 'function') {
32
+ try {
33
+ return React.use(record);
34
+ } catch (x) {
35
+ if (x === null) {
36
+ return null;
37
+ }
38
+ throw x;
39
+ }
40
+ }
41
+ if (record.status === 'fulfilled') {
42
+ return record.value;
43
+ } else if (record.status === 'rejected') {
44
+ return null;
45
} else {
54
- throw record.value;
46
+ throw record;
47
}
48
}
49
@@ -66,10 +58,15 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module {
58
}
59
60
if (!record) {
69
- const callbacks = new Set<() => mixed>();
70
- const wakeable: Wakeable = {
71
- then(callback: () => mixed) {
61
+ const callbacks = new Set<(value: any) => mixed>();
62
+ const rejectCallbacks = new Set<(reason: mixed) => mixed>();
63
+ const thenable: Thenable<Module> = {
64
+ status: 'pending',
65
+ value: null,
66
+ reason: null,
67
+ then(callback: (value: any) => mixed, reject: (error: mixed) => mixed) {
68
callbacks.add(callback);
69
+ rejectCallbacks.add(reject);
70
},
71
72
// Optional property used by Timeline:
@@ -85,12 +82,21 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module {
82
// This assumes they won't throw.
83
callbacks.forEach(callback => callback());
84
callbacks.clear();
85
+ rejectCallbacks.clear();
86
};
87
+ const wakeRejections = () => {
88
+ if (timeoutID) {
89
+ clearTimeout(timeoutID);
90
+ timeoutID = null;
91
+ }
92
90
- const newRecord: Record<Module> = (record = {
91
- status: Pending,
92
- value: wakeable,
93
- });
93
+ // This assumes they won't throw.
94
+ rejectCallbacks.forEach(callback => callback((thenable: any).reason));
95
+ rejectCallbacks.clear();
96
+ callbacks.clear();
97
+ };
98
+
99
+ record = thenable;
100
101
let didTimeout = false;
102
@@ -106,9 +112,9 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module {
112
return;
113
}
114
109
- const resolvedRecord = ((newRecord: any): ResolvedRecord<Module>);
110
- resolvedRecord.status = Resolved;
111
- resolvedRecord.value = module;
115
+ const fulfilledThenable: FulfilledThenable<Module> = (thenable: any);
116
+ fulfilledThenable.status = 'fulfilled';
117
+ fulfilledThenable.value = module;
118
119
wake();
120
},
@@ -125,11 +131,11 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module {
131
132
console.log(error);
133
128
- const thrownRecord = ((newRecord: any): RejectedRecord);
129
- thrownRecord.status = Rejected;
130
- thrownRecord.value = null;
134
+ const rejectedThenable: RejectedThenable<Module> = (thenable: any);
135
+ rejectedThenable.status = 'rejected';
136
+ rejectedThenable.reason = error;
137
132
- wake();
138
+ wakeRejections();
139
},
140
);
141
@@ -145,17 +151,17 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module {
151
152
didTimeout = true;
153
148
- const timedoutRecord = ((newRecord: any): RejectedRecord);
149
- timedoutRecord.status = Rejected;
150
- timedoutRecord.value = null;
154
+ const rejectedThenable: RejectedThenable<Module> = (thenable: any);
155
+ rejectedThenable.status = 'rejected';
156
+ rejectedThenable.reason = null;
157
152
- wake();
158
+ wakeRejections();
159
}, TIMEOUT);
160
161
moduleLoaderFunctionToModuleMap.set(moduleLoaderFunction, record);
162
}
163
164
// $FlowFixMe[underconstrained-implicit-instantiation]
159
- const response = readRecord(record).value;
165
+ const response = readRecord(record);
166
return response;
167
}
packages/react-devtools-shared/src/hookNamesCache.js
+74
-76
@@ -10,49 +10,40 @@
10
import {__DEBUG__} from 'react-devtools-shared/src/constants';
11
12
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
13
-import type {Thenable, Wakeable} from 'shared/ReactTypes';
13
+import type {
14
+ Thenable,
15
+ FulfilledThenable,
16
+ RejectedThenable,
17
+} from 'shared/ReactTypes';
18
import type {
19
Element,
20
HookNames,
17
- HookSourceLocationKey,
21
} from 'react-devtools-shared/src/frontend/types';
19
-import type {HookSource} from 'react-debug-tools/src/ReactDebugHooks';
22
import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
23
+
24
+import * as React from 'react';
25
+
26
import {withCallbackPerfMeasurements} from './PerformanceLoggingUtils';
27
import {logEvent} from './Logger';
28
29
const TIMEOUT = 30000;
25
-
26
-const Pending = 0;
27
-const Resolved = 1;
28
-const Rejected = 2;
29
-
30
-type PendingRecord = {
31
- status: 0,
32
- value: Wakeable,
33
-};
34
-
35
-type ResolvedRecord<T> = {
36
- status: 1,
37
- value: T,
38
-};
39
-
40
-type RejectedRecord = {
41
- status: 2,
42
- value: null,
43
-};
44
-
45
-type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
46
-
47
-function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
48
- if (record.status === Resolved) {
49
- // This is just a type refinement.
50
- return record;
51
- } else if (record.status === Rejected) {
52
- // This is just a type refinement.
53
- return record;
30
+function readRecord<T>(record: Thenable<T>): T | null {
31
+ if (typeof React.use === 'function') {
32
+ try {
33
+ return React.use(record);
34
+ } catch (x) {
35
+ if (record.status === 'rejected') {
36
+ return null;
37
+ }
38
+ throw x;
39
+ }
40
+ }
41
+ if (record.status === 'fulfilled') {
42
+ return record.value;
43
+ } else if (record.status === 'rejected') {
44
+ return null;
45
} else {
55
- throw record.value;
46
+ throw record;
47
}
48
}
49
@@ -65,16 +56,16 @@ type LoadHookNamesFunction = (
56
// Otherwise, refreshing the inspected element cache would also clear this cache.
57
// TODO Rethink this if the React API constraints change.
58
// See https://github.com/reactwg/react-18/discussions/25#discussioncomment-980435
68
-let map: WeakMap<Element, Record<HookNames>> = new WeakMap();
59
+let map: WeakMap<Element, Thenable<HookNames>> = new WeakMap();
60
61
export function hasAlreadyLoadedHookNames(element: Element): boolean {
62
const record = map.get(element);
72
- return record != null && record.status === Resolved;
63
+ return record != null && record.status === 'fulfilled';
64
}
65
66
export function getAlreadyLoadedHookNames(element: Element): HookNames | null {
67
const record = map.get(element);
77
- if (record != null && record.status === Resolved) {
68
+ if (record != null && record.status === 'fulfilled') {
69
return record.value;
70
}
71
return null;
@@ -95,10 +86,15 @@ export function loadHookNames(
86
}
87
88
if (!record) {
98
- const callbacks = new Set<() => mixed>();
99
- const wakeable: Wakeable = {
100
- then(callback: () => mixed) {
89
+ const callbacks = new Set<(value: any) => mixed>();
90
+ const rejectCallbacks = new Set<(reason: mixed) => mixed>();
91
+ const thenable: Thenable<HookNames> = {
92
+ status: 'pending',
93
+ value: null,
94
+ reason: null,
95
+ then(callback: (value: any) => mixed, reject: (error: mixed) => mixed) {
96
callbacks.add(callback);
97
+ rejectCallbacks.add(reject);
98
},
99
100
// Optional property used by Timeline:
@@ -117,7 +113,18 @@ export function loadHookNames(
113
}
114
115
// This assumes they won't throw.
120
- callbacks.forEach(callback => callback());
116
+ callbacks.forEach(callback => callback((thenable: any).value));
117
+ callbacks.clear();
118
+ rejectCallbacks.clear();
119
+ };
120
+ const wakeRejections = () => {
121
+ if (timeoutID) {
122
+ clearTimeout(timeoutID);
123
+ timeoutID = null;
124
+ }
125
+ // This assumes they won't throw.
126
+ rejectCallbacks.forEach(callback => callback((thenable: any).reason));
127
+ rejectCallbacks.clear();
128
callbacks.clear();
129
};
130
@@ -132,10 +139,7 @@ export function loadHookNames(
139
});
140
};
141
135
- const newRecord: Record<HookNames> = (record = {
136
- status: Pending,
137
- value: wakeable,
138
- });
142
+ record = thenable;
143
144
withCallbackPerfMeasurements(
145
'loadHookNames',
@@ -151,20 +155,24 @@ export function loadHookNames(
155
}
156
157
if (hookNames) {
154
- const resolvedRecord =
155
- ((newRecord: any): ResolvedRecord<HookNames>);
156
- resolvedRecord.status = Resolved;
157
- resolvedRecord.value = hookNames;
158
+ const fulfilledThenable: FulfilledThenable<HookNames> =
159
+ (thenable: any);
160
+ fulfilledThenable.status = 'fulfilled';
161
+ fulfilledThenable.value = hookNames;
162
+ status = 'success';
163
+ resolvedHookNames = hookNames;
164
+ done();
165
+ wake();
166
} else {
159
- const notFoundRecord = ((newRecord: any): RejectedRecord);
160
- notFoundRecord.status = Rejected;
161
- notFoundRecord.value = null;
167
+ const notFoundThenable: RejectedThenable<HookNames> =
168
+ (thenable: any);
169
+ notFoundThenable.status = 'rejected';
170
+ notFoundThenable.reason = null;
171
+ status = 'error';
172
+ resolvedHookNames = hookNames;
173
+ done();
174
+ wakeRejections();
175
}
163
-
164
- status = 'success';
165
- resolvedHookNames = hookNames;
166
- done();
167
- wake();
176
},
177
function onError(error) {
178
if (didTimeout) {
@@ -177,13 +185,14 @@ export function loadHookNames(
185
186
console.error(error);
187
180
- const thrownRecord = ((newRecord: any): RejectedRecord);
181
- thrownRecord.status = Rejected;
182
- thrownRecord.value = null;
188
+ const rejectedThenable: RejectedThenable<HookNames> =
189
+ (thenable: any);
190
+ rejectedThenable.status = 'rejected';
191
+ rejectedThenable.reason = null;
192
193
status = 'error';
194
done();
186
- wake();
195
+ wakeRejections();
196
},
197
);
198
@@ -197,13 +206,13 @@ export function loadHookNames(
206
207
didTimeout = true;
208
200
- const timedoutRecord = ((newRecord: any): RejectedRecord);
201
- timedoutRecord.status = Rejected;
202
- timedoutRecord.value = null;
209
+ const timedoutThenable: RejectedThenable<HookNames> = (thenable: any);
210
+ timedoutThenable.status = 'rejected';
211
+ timedoutThenable.reason = null;
212
213
status = 'timeout';
214
done();
206
- wake();
215
+ wakeRejections();
216
}, TIMEOUT);
217
},
218
handleLoadComplete,
@@ -211,21 +220,10 @@ export function loadHookNames(
220
map.set(element, record);
221
}
222
214
- const response = readRecord(record).value;
223
+ const response = readRecord(record);
224
return response;
225
}
226
218
-export function getHookSourceLocationKey({
219
- fileName,
220
- lineNumber,
221
- columnNumber,
222
-}: HookSource): HookSourceLocationKey {
223
- if (fileName == null || lineNumber == null || columnNumber == null) {
224
- throw Error('Hook source code location not found.');
225
- }
226
- return `${fileName}:${lineNumber}:${columnNumber}`;
227
-}
228
-
227
export function clearHookNamesCache(): void {
228
map = new WeakMap();
229
}
packages/react-devtools-shared/src/hookSourceLocation.js
new
+22
@@ -0,0 +1,22 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {HookSourceLocationKey} from 'react-devtools-shared/src/frontend/types';
11
+import type {HookSource} from 'react-debug-tools/src/ReactDebugHooks';
12
+
13
+export function getHookSourceLocationKey({
14
+ fileName,
15
+ lineNumber,
16
+ columnNumber,
17
+}: HookSource): HookSourceLocationKey {
18
+ if (fileName == null || lineNumber == null || columnNumber == null) {
19
+ throw Error('Hook source code location not found.');
20
+ }
21
+ return `${fileName}:${lineNumber}:${columnNumber}`;
22
+}
packages/react-devtools-shared/src/hooks/parseHookNames/index.js
+2
@@ -12,6 +12,8 @@ import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
12
import type {HookNames} from 'react-devtools-shared/src/frontend/types';
13
import type {FetchFileWithCaching} from 'react-devtools-shared/src/devtools/views/Components/FetchFileWithCachingContext';
14
15
+import 'react';
16
+
17
import {withAsyncPerfMeasurements} from 'react-devtools-shared/src/PerformanceLoggingUtils';
18
import WorkerizedParseSourceAndMetadata from './parseSourceAndMetadata.worker';
19
import typeof * as ParseSourceAndMetadataModule from './parseSourceAndMetadata';
packages/react-devtools-shared/src/hooks/parseHookNames/loadSourceAndMetadata.js
+1
-1
@@ -46,7 +46,7 @@
46
// and there is no need to convert runtime code to the original source.
47
48
import {__DEBUG__} from 'react-devtools-shared/src/constants';
49
-import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
49
+import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookSourceLocation';
50
import {sourceMapIncludesSource} from '../SourceMapUtils';
51
import {
52
withAsyncPerfMeasurements,
packages/react-devtools-shared/src/hooks/parseHookNames/parseSourceAndMetadata.js
+1
-1
@@ -15,7 +15,7 @@ import LRU from 'lru-cache';
15
import {getHookName} from '../astUtils';
16
import {areSourceMapsAppliedToErrors} from '../ErrorTester';
17
import {__DEBUG__} from 'react-devtools-shared/src/constants';
18
-import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
18
+import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookSourceLocation';
19
import {SourceMapMetadataConsumer} from '../SourceMapMetadataConsumer';
20
import {
21
withAsyncPerfMeasurements,
packages/react-devtools-shared/src/inspectedElementCache.js
+55
-53
@@ -7,6 +7,8 @@
7
* @flow
8
*/
9
10
+import * as React from 'react';
11
+
12
import {
13
unstable_getCacheForType as getCacheForType,
14
startTransition,
@@ -16,7 +18,11 @@ import {inspectElement as inspectElementMutableSource} from 'react-devtools-shar
18
import ElementPollingCancellationError from 'react-devtools-shared/src//errors/ElementPollingCancellationError';
19
20
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
19
-import type {Wakeable} from 'shared/ReactTypes';
21
+import type {
22
+ Thenable,
23
+ FulfilledThenable,
24
+ RejectedThenable,
25
+} from 'shared/ReactTypes';
26
import type {
27
Element,
28
InspectedElement as InspectedElementFrontend,
@@ -24,44 +30,27 @@ import type {
30
InspectedElementPath,
31
} from 'react-devtools-shared/src/frontend/types';
32
27
-const Pending = 0;
28
-const Resolved = 1;
29
-const Rejected = 2;
30
-
31
-type PendingRecord = {
32
- status: 0,
33
- value: Wakeable,
34
-};
35
-
36
-type ResolvedRecord<T> = {
37
- status: 1,
38
- value: T,
39
-};
40
-
41
-type RejectedRecord = {
42
- status: 2,
43
- value: Error | string,
44
-};
45
-
46
-type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
47
-
48
-function readRecord<T>(record: Record<T>): ResolvedRecord<T> {
49
- if (record.status === Resolved) {
50
- // This is just a type refinement.
51
- return record;
33
+function readRecord<T>(record: Thenable<T>): T {
34
+ if (typeof React.use === 'function') {
35
+ return React.use(record);
36
+ }
37
+ if (record.status === 'fulfilled') {
38
+ return record.value;
39
+ } else if (record.status === 'rejected') {
40
+ throw record.reason;
41
} else {
53
- throw record.value;
42
+ throw record;
43
}
44
}
45
57
-type InspectedElementMap = WeakMap<Element, Record<InspectedElementFrontend>>;
46
+type InspectedElementMap = WeakMap<Element, Thenable<InspectedElementFrontend>>;
47
type CacheSeedKey = () => InspectedElementMap;
48
49
function createMap(): InspectedElementMap {
50
return new WeakMap();
51
}
52
64
-function getRecordMap(): WeakMap<Element, Record<InspectedElementFrontend>> {
53
+function getRecordMap(): WeakMap<Element, Thenable<InspectedElementFrontend>> {
54
return getCacheForType(createMap);
55
}
56
@@ -69,12 +58,15 @@ function createCacheSeed(
58
element: Element,
59
inspectedElement: InspectedElementFrontend,
60
): [CacheSeedKey, InspectedElementMap] {
72
- const newRecord: Record<InspectedElementFrontend> = {
73
- status: Resolved,
61
+ const thenable: FulfilledThenable<InspectedElementFrontend> = {
62
+ then(callback: (value: any) => mixed, reject: (error: mixed) => mixed) {
63
+ callback(thenable.value);
64
+ },
65
+ status: 'fulfilled',
66
value: inspectedElement,
67
};
68
const map = createMap();
77
- map.set(element, newRecord);
69
+ map.set(element, thenable);
70
return [createMap, map];
71
}
72
@@ -91,10 +83,15 @@ export function inspectElement(
83
const map = getRecordMap();
84
let record = map.get(element);
85
if (!record) {
94
- const callbacks = new Set<() => mixed>();
95
- const wakeable: Wakeable = {
96
- then(callback: () => mixed) {
86
+ const callbacks = new Set<(value: any) => mixed>();
87
+ const rejectCallbacks = new Set<(reason: mixed) => mixed>();
88
+ const thenable: Thenable<InspectedElementFrontend> = {
89
+ status: 'pending',
90
+ value: null,
91
+ reason: null,
92
+ then(callback: (value: any) => mixed, reject: (error: mixed) => mixed) {
93
callbacks.add(callback);
94
+ rejectCallbacks.add(reject);
95
},
96
97
// Optional property used by Timeline:
@@ -103,19 +100,24 @@ export function inspectElement(
100
101
const wake = () => {
102
// This assumes they won't throw.
106
- callbacks.forEach(callback => callback());
103
+ callbacks.forEach(callback => callback((thenable: any).value));
104
callbacks.clear();
105
+ rejectCallbacks.clear();
106
};
109
- const newRecord: Record<InspectedElementFrontend> = (record = {
110
- status: Pending,
111
- value: wakeable,
112
- });
107
+ const wakeRejections = () => {
108
+ // This assumes they won't throw.
109
+ rejectCallbacks.forEach(callback => callback((thenable: any).reason));
110
+ rejectCallbacks.clear();
111
+ callbacks.clear();
112
+ };
113
+ record = thenable;
114
115
const rendererID = store.getRendererIDForElement(element.id);
116
if (rendererID == null) {
116
- const rejectedRecord = ((newRecord: any): RejectedRecord);
117
- rejectedRecord.status = Rejected;
118
- rejectedRecord.value = new Error(
117
+ const rejectedThenable: RejectedThenable<InspectedElementFrontend> =
118
+ (thenable: any);
119
+ rejectedThenable.status = 'rejected';
120
+ rejectedThenable.reason = new Error(
121
`Could not inspect element with id "${element.id}". No renderer found.`,
122
);
123
@@ -129,29 +131,29 @@ export function inspectElement(
131
InspectedElementFrontend,
132
InspectedElementResponseType,
133
]) => {
132
- const resolvedRecord =
133
- ((newRecord: any): ResolvedRecord<InspectedElementFrontend>);
134
- resolvedRecord.status = Resolved;
135
- resolvedRecord.value = inspectedElement;
136
-
134
+ const fulfilledThenable: FulfilledThenable<InspectedElementFrontend> =
135
+ (thenable: any);
136
+ fulfilledThenable.status = 'fulfilled';
137
+ fulfilledThenable.value = inspectedElement;
138
wake();
139
},
140
141
error => {
142
console.error(error);
143
143
- const rejectedRecord = ((newRecord: any): RejectedRecord);
144
- rejectedRecord.status = Rejected;
145
- rejectedRecord.value = error;
144
+ const rejectedThenable: RejectedThenable<InspectedElementFrontend> =
145
+ (thenable: any);
146
+ rejectedThenable.status = 'rejected';
147
+ rejectedThenable.reason = error;
148
147
- wake();
149
+ wakeRejections();
150
},
151
);
152
153
map.set(element, record);
154
}
155
154
- const response = readRecord(record).value;
156
+ const response = readRecord(record);
157
return response;
158
}
159
packages/react-devtools-timeline/src/timelineCache.js
+56
-50
@@ -7,46 +7,42 @@
7
* @flow
8
*/
9
10
-import type {Wakeable} from 'shared/ReactTypes';
10
+import type {
11
+ Thenable,
12
+ FulfilledThenable,
13
+ RejectedThenable,
14
+} from 'shared/ReactTypes';
15
import type {TimelineData} from './types';
16
13
-import {importFile as importFileWorker} from './import-worker';
14
-
15
-const Pending = 0;
16
-const Resolved = 1;
17
-const Rejected = 2;
18
-
19
-type PendingRecord = {
20
- status: 0,
21
- value: Wakeable,
22
-};
23
-
24
-type ResolvedRecord<T> = {
25
- status: 1,
26
- value: T,
27
-};
17
+import * as React from 'react';
18
29
-type RejectedRecord = {
30
- status: 2,
31
- value: Error,
32
-};
33
-
34
-type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
19
+import {importFile as importFileWorker} from './import-worker';
20
21
// This is intentionally a module-level Map, rather than a React-managed one.
22
// Otherwise, refreshing the inspected element cache would also clear this cache.
23
// Profiler file contents are static anyway.
39
-const fileNameToProfilerDataMap: Map<string, Record<TimelineData>> = new Map();
40
-
41
-function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
42
- if (record.status === Resolved) {
43
- // This is just a type refinement.
44
- return record;
45
- } else if (record.status === Rejected) {
46
- // This is just a type refinement.
47
- return record;
24
+const fileNameToProfilerDataMap: Map<
25
+ string,
26
+ Thenable<TimelineData>,
27
+> = new Map();
28
+
29
+function readRecord<T>(record: Thenable<T>): T | Error {
30
+ if (typeof React.use === 'function') {
31
+ try {
32
+ return React.use(record);
33
+ } catch (x) {
34
+ if (record.status === 'rejected') {
35
+ return (record.reason: any);
36
+ }
37
+ throw x;
38
+ }
39
+ }
40
+ if (record.status === 'fulfilled') {
41
+ return record.value;
42
+ } else if (record.status === 'rejected') {
43
+ return (record.reason: any);
44
} else {
49
- throw record.value;
45
+ throw record;
46
}
47
}
48
@@ -55,10 +51,15 @@ export function importFile(file: File): TimelineData | Error {
51
let record = fileNameToProfilerDataMap.get(fileName);
52
53
if (!record) {
58
- const callbacks = new Set<() => mixed>();
59
- const wakeable: Wakeable = {
60
- then(callback: () => mixed) {
54
+ const callbacks = new Set<(value: any) => mixed>();
55
+ const rejectCallbacks = new Set<(reason: mixed) => mixed>();
56
+ const thenable: Thenable<TimelineData> = {
57
+ status: 'pending',
58
+ value: null,
59
+ reason: null,
60
+ then(callback: (value: any) => mixed, reject: (error: mixed) => mixed) {
61
callbacks.add(callback);
62
+ rejectCallbacks.add(reject);
63
},
64
65
// Optional property used by Timeline:
@@ -67,37 +68,42 @@ export function importFile(file: File): TimelineData | Error {
68
69
const wake = () => {
70
// This assumes they won't throw.
70
- callbacks.forEach(callback => callback());
71
+ callbacks.forEach(callback => callback((thenable: any).value));
72
+ callbacks.clear();
73
+ rejectCallbacks.clear();
74
+ };
75
+ const wakeRejections = () => {
76
+ // This assumes they won't throw.
77
+ rejectCallbacks.forEach(callback => callback((thenable: any).reason));
78
+ rejectCallbacks.clear();
79
callbacks.clear();
80
};
81
74
- const newRecord: Record<TimelineData> = (record = {
75
- status: Pending,
76
- value: wakeable,
77
- });
82
+ record = thenable;
83
84
importFileWorker(file).then(data => {
85
switch (data.status) {
86
case 'SUCCESS':
82
- const resolvedRecord =
83
- ((newRecord: any): ResolvedRecord<TimelineData>);
84
- resolvedRecord.status = Resolved;
85
- resolvedRecord.value = data.processedData;
87
+ const fulfilledThenable: FulfilledThenable<TimelineData> =
88
+ (thenable: any);
89
+ fulfilledThenable.status = 'fulfilled';
90
+ fulfilledThenable.value = data.processedData;
91
+ wake();
92
break;
93
case 'INVALID_PROFILE_ERROR':
94
case 'UNEXPECTED_ERROR':
89
- const thrownRecord = ((newRecord: any): RejectedRecord);
90
- thrownRecord.status = Rejected;
91
- thrownRecord.value = data.error;
95
+ const rejectedThenable: RejectedThenable<TimelineData> =
96
+ (thenable: any);
97
+ rejectedThenable.status = 'rejected';
98
+ rejectedThenable.reason = data.error;
99
+ wakeRejections();
100
break;
101
}
94
-
95
- wake();
102
});
103
104
fileNameToProfilerDataMap.set(fileName, record);
105
}
106
101
- const response = readRecord(record).value;
107
+ const response = readRecord(record);
108
return response;
109
}