@samitouri / QOS-React / commits / 565eb7888e

Unwrap a reference to a Lazy value (#34535)

If we are referencing a lazy value that isn't explicitly lazy ($L...) it's because we added it around an element that was blocked to be able to defer things inside. However, once that is unblocked we can start unwrap it and just use the inner element instead for any future reference. The race condition is still there since it's a race condition whether we added the wrapper in the first place. This just makes it consistent with unwrapping of the rest of the path.

Sebastian Markbåge committed Sep 19, 2025 at 18:23 UTC 565eb7888efa5d8e70273d9b7a16374131348cbb
1 file changed +69 -2
packages/react-client/src/ReactFlightClient.js
+69 -2
@@ -1337,7 +1337,11 @@ function fulfillReference(
1337 const {response, handler, parentObject, key, map, path} = reference;
1338
1339 for (let i = 1; i < path.length; i++) {
1340 - while (value.$$typeof === REACT_LAZY_TYPE) {
1340 + while (
1341 + typeof value === 'object' &&
1342 + value !== null &&
1343 + value.$$typeof === REACT_LAZY_TYPE
1344 + ) {
1345 // We never expect to see a Lazy node on this path because we encode those as
1346 // separate models. This must mean that we have inserted an extra lazy node
1347 // e.g. to replace a blocked element. We must instead look for it inside.
@@ -1408,6 +1412,39 @@ function fulfillReference(
1412 }
1413 value = value[path[i]];
1414 }
1415 +
1416 + while (
1417 + typeof value === 'object' &&
1418 + value !== null &&
1419 + value.$$typeof === REACT_LAZY_TYPE
1420 + ) {
1421 + // If what we're referencing is a Lazy it must be because we inserted one as a virtual node
1422 + // while it was blocked by other data. If it's no longer blocked, we can unwrap it.
1423 + const referencedChunk: SomeChunk<any> = value._payload;
1424 + if (referencedChunk === handler.chunk) {
1425 + // This is a reference to the thing we're currently blocking. We can peak
1426 + // inside of it to get the value.
1427 + value = handler.value;
1428 + continue;
1429 + } else {
1430 + switch (referencedChunk.status) {
1431 + case RESOLVED_MODEL:
1432 + initializeModelChunk(referencedChunk);
1433 + break;
1434 + case RESOLVED_MODULE:
1435 + initializeModuleChunk(referencedChunk);
1436 + break;
1437 + }
1438 + switch (referencedChunk.status) {
1439 + case INITIALIZED: {
1440 + value = referencedChunk.value;
1441 + continue;
1442 + }
1443 + }
1444 + }
1445 + break;
1446 + }
1447 +
1448 const mappedValue = map(response, value, parentObject, key);
1449 parentObject[key] = mappedValue;
1450
@@ -1855,7 +1892,11 @@ function getOutlinedModel<T>(
1892 case INITIALIZED:
1893 let value = chunk.value;
1894 for (let i = 1; i < path.length; i++) {
1858 - while (value.$$typeof === REACT_LAZY_TYPE) {
1895 + while (
1896 + typeof value === 'object' &&
1897 + value !== null &&
1898 + value.$$typeof === REACT_LAZY_TYPE
1899 + ) {
1900 const referencedChunk: SomeChunk<any> = value._payload;
1901 switch (referencedChunk.status) {
1902 case RESOLVED_MODEL:
@@ -1924,6 +1965,32 @@ function getOutlinedModel<T>(
1965 }
1966 value = value[path[i]];
1967 }
1968 +
1969 + while (
1970 + typeof value === 'object' &&
1971 + value !== null &&
1972 + value.$$typeof === REACT_LAZY_TYPE
1973 + ) {
1974 + // If what we're referencing is a Lazy it must be because we inserted one as a virtual node
1975 + // while it was blocked by other data. If it's no longer blocked, we can unwrap it.
1976 + const referencedChunk: SomeChunk<any> = value._payload;
1977 + switch (referencedChunk.status) {
1978 + case RESOLVED_MODEL:
1979 + initializeModelChunk(referencedChunk);
1980 + break;
1981 + case RESOLVED_MODULE:
1982 + initializeModuleChunk(referencedChunk);
1983 + break;
1984 + }
1985 + switch (referencedChunk.status) {
1986 + case INITIALIZED: {
1987 + value = referencedChunk.value;
1988 + continue;
1989 + }
1990 + }
1991 + break;
1992 + }
1993 +
1994 const chunkValue = map(response, value, parentObject, key);
1995 if (
1996 parentObject[0] === REACT_ELEMENT_TYPE &&