@samitouri / QOS-React-1 / commits / 7268dacf70

Devtools: Ensure component control flow is consistent with commit when using `useDeferredValue (#28508)

Sebastian Silbermann committed Mar 14, 2024 at 15:41 UTC 7268dacf70cd6a7f5705a19053aad46b5289ab72
2 files changed +261 -2
packages/react-debug-tools/src/ReactDebugHooks.js
+3 -2
@@ -444,14 +444,15 @@ function useTransition(): [
444
445 function useDeferredValue<T>(value: T, initialValue?: T): T {
446 const hook = nextHook();
447 + const prevValue = hook !== null ? hook.memoizedState : value;
448 hookLog.push({
449 displayName: null,
450 primitive: 'DeferredValue',
451 stackError: new Error(),
451 - value: hook !== null ? hook.memoizedState : value,
452 + value: prevValue,
453 debugInfo: null,
454 });
454 - return value;
455 + return prevValue;
456 }
457
458 function useId(): string {
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+258
@@ -1206,6 +1206,264 @@ describe('ReactHooksInspectionIntegration', () => {
1206 `);
1207 });
1208
1209 + it('should return the deferred value', async () => {
1210 + let unsuspend;
1211 + function Lazy() {
1212 + return 'Lazy';
1213 + }
1214 + const Suspender = React.lazy(
1215 + () =>
1216 + new Promise(resolve => {
1217 + unsuspend = () => resolve({default: Lazy});
1218 + }),
1219 + );
1220 + const Context = React.createContext('default');
1221 + let setShow;
1222 + function Foo(props) {
1223 + const [show, _setShow] = React.useState(false);
1224 + const deferredShow = React.useDeferredValue(show);
1225 + const isPending = show !== deferredShow;
1226 + const contextDisplay = isPending ? React.use(Context) : '<none>';
1227 + React.useMemo(() => 'hello', []);
1228 + React.useMemo(() => 'not used', []);
1229 +
1230 + // Otherwise we capture the version from the react-debug-tools dispatcher.
1231 + if (setShow === undefined) {
1232 + setShow = _setShow;
1233 + }
1234 +
1235 + return (
1236 + <React.Suspense fallback="Loading">
1237 + Context: {contextDisplay}, {isPending ? 'Pending' : 'Nothing Pending'}
1238 + {deferredShow ? [', ', <Suspender key="suspender" />] : null}
1239 + </React.Suspense>
1240 + );
1241 + }
1242 + const renderer = await act(() => {
1243 + return ReactTestRenderer.create(
1244 + <Context.Provider value="provided">
1245 + <Foo />
1246 + </Context.Provider>,
1247 + {isConcurrent: true},
1248 + );
1249 + });
1250 + let childFiber = renderer.root.findByType(Foo)._currentFiber();
1251 + let tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
1252 + expect(renderer).toMatchRenderedOutput('Context: <none>, Nothing Pending');
1253 + expect(normalizeSourceLoc(tree)).toMatchInlineSnapshot(`
1254 + [
1255 + {
1256 + "debugInfo": null,
1257 + "hookSource": {
1258 + "columnNumber": 0,
1259 + "fileName": "**",
1260 + "functionName": "Foo",
1261 + "lineNumber": 0,
1262 + },
1263 + "id": 0,
1264 + "isStateEditable": true,
1265 + "name": "State",
1266 + "subHooks": [],
1267 + "value": false,
1268 + },
1269 + {
1270 + "debugInfo": null,
1271 + "hookSource": {
1272 + "columnNumber": 0,
1273 + "fileName": "**",
1274 + "functionName": "Foo",
1275 + "lineNumber": 0,
1276 + },
1277 + "id": 1,
1278 + "isStateEditable": false,
1279 + "name": "DeferredValue",
1280 + "subHooks": [],
1281 + "value": false,
1282 + },
1283 + {
1284 + "debugInfo": null,
1285 + "hookSource": {
1286 + "columnNumber": 0,
1287 + "fileName": "**",
1288 + "functionName": "Foo",
1289 + "lineNumber": 0,
1290 + },
1291 + "id": 2,
1292 + "isStateEditable": false,
1293 + "name": "Memo",
1294 + "subHooks": [],
1295 + "value": "hello",
1296 + },
1297 + {
1298 + "debugInfo": null,
1299 + "hookSource": {
1300 + "columnNumber": 0,
1301 + "fileName": "**",
1302 + "functionName": "Foo",
1303 + "lineNumber": 0,
1304 + },
1305 + "id": 3,
1306 + "isStateEditable": false,
1307 + "name": "Memo",
1308 + "subHooks": [],
1309 + "value": "not used",
1310 + },
1311 + ]
1312 + `);
1313 +
1314 + await act(() => {
1315 + setShow(true);
1316 + });
1317 +
1318 + expect(renderer).toMatchRenderedOutput('Context: provided, Pending');
1319 + childFiber = renderer.root.findByType(Foo)._currentFiber();
1320 + tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
1321 + expect(normalizeSourceLoc(tree)).toMatchInlineSnapshot(`
1322 + [
1323 + {
1324 + "debugInfo": null,
1325 + "hookSource": {
1326 + "columnNumber": 0,
1327 + "fileName": "**",
1328 + "functionName": "Foo",
1329 + "lineNumber": 0,
1330 + },
1331 + "id": 0,
1332 + "isStateEditable": true,
1333 + "name": "State",
1334 + "subHooks": [],
1335 + "value": true,
1336 + },
1337 + {
1338 + "debugInfo": null,
1339 + "hookSource": {
1340 + "columnNumber": 0,
1341 + "fileName": "**",
1342 + "functionName": "Foo",
1343 + "lineNumber": 0,
1344 + },
1345 + "id": 1,
1346 + "isStateEditable": false,
1347 + "name": "DeferredValue",
1348 + "subHooks": [],
1349 + "value": false,
1350 + },
1351 + {
1352 + "debugInfo": null,
1353 + "hookSource": {
1354 + "columnNumber": 0,
1355 + "fileName": "**",
1356 + "functionName": "Foo",
1357 + "lineNumber": 0,
1358 + },
1359 + "id": null,
1360 + "isStateEditable": false,
1361 + "name": "Context",
1362 + "subHooks": [],
1363 + "value": "provided",
1364 + },
1365 + {
1366 + "debugInfo": null,
1367 + "hookSource": {
1368 + "columnNumber": 0,
1369 + "fileName": "**",
1370 + "functionName": "Foo",
1371 + "lineNumber": 0,
1372 + },
1373 + "id": 2,
1374 + "isStateEditable": false,
1375 + "name": "Memo",
1376 + "subHooks": [],
1377 + "value": "hello",
1378 + },
1379 + {
1380 + "debugInfo": null,
1381 + "hookSource": {
1382 + "columnNumber": 0,
1383 + "fileName": "**",
1384 + "functionName": "Foo",
1385 + "lineNumber": 0,
1386 + },
1387 + "id": 3,
1388 + "isStateEditable": false,
1389 + "name": "Memo",
1390 + "subHooks": [],
1391 + "value": "not used",
1392 + },
1393 + ]
1394 + `);
1395 +
1396 + await act(() => {
1397 + unsuspend();
1398 + });
1399 +
1400 + expect(renderer).toMatchRenderedOutput(
1401 + 'Context: <none>, Nothing Pending, Lazy',
1402 + );
1403 + childFiber = renderer.root.findByType(Foo)._currentFiber();
1404 + tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
1405 + expect(normalizeSourceLoc(tree)).toMatchInlineSnapshot(`
1406 + [
1407 + {
1408 + "debugInfo": null,
1409 + "hookSource": {
1410 + "columnNumber": 0,
1411 + "fileName": "**",
1412 + "functionName": "Foo",
1413 + "lineNumber": 0,
1414 + },
1415 + "id": 0,
1416 + "isStateEditable": true,
1417 + "name": "State",
1418 + "subHooks": [],
1419 + "value": true,
1420 + },
1421 + {
1422 + "debugInfo": null,
1423 + "hookSource": {
1424 + "columnNumber": 0,
1425 + "fileName": "**",
1426 + "functionName": "Foo",
1427 + "lineNumber": 0,
1428 + },
1429 + "id": 1,
1430 + "isStateEditable": false,
1431 + "name": "DeferredValue",
1432 + "subHooks": [],
1433 + "value": true,
1434 + },
1435 + {
1436 + "debugInfo": null,
1437 + "hookSource": {
1438 + "columnNumber": 0,
1439 + "fileName": "**",
1440 + "functionName": "Foo",
1441 + "lineNumber": 0,
1442 + },
1443 + "id": 2,
1444 + "isStateEditable": false,
1445 + "name": "Memo",
1446 + "subHooks": [],
1447 + "value": "hello",
1448 + },
1449 + {
1450 + "debugInfo": null,
1451 + "hookSource": {
1452 + "columnNumber": 0,
1453 + "fileName": "**",
1454 + "functionName": "Foo",
1455 + "lineNumber": 0,
1456 + },
1457 + "id": 3,
1458 + "isStateEditable": false,
1459 + "name": "Memo",
1460 + "subHooks": [],
1461 + "value": "not used",
1462 + },
1463 + ]
1464 + `);
1465 + });
1466 +
1467 it('should support useId hook', () => {
1468 function Foo(props) {
1469 const id = React.useId();