@samitouri / QOS-React / commits / 76002254b7

Fix resolving of references to deduped props in lazy elements (#30441)

When a model references a deduped object of a blocked element that has subsequently been turned into a lazy element, we need to wait for the lazy element's chunk to resolve before resolving the reference. Without the fix, the new test failed with the following runtime error: ``` TypeError: Cannot read properties of undefined (reading 'children') 1003 | let value = chunk.value; 1004 | for (let i = 1; i < path.length; i++) { > 1005 | value = value[path[i]]; | ^ 1006 | } 1007 | const chunkValue = map(response, value); 1008 | if (__DEV__ && chunk._debugInfo) { at getOutlinedModel (packages/react-client/src/ReactFlightClient.js:1005:26) ``` The bug was uncovered after updating React in Next.js in https://github.com/vercel/next.js/pull/66711.

Hendrik Liebau committed Jul 25, 2024 at 01:34 UTC 76002254b7e3270da199eec1e6f9a0577b988a36
2 files changed +85 -1
packages/react-client/src/ReactFlightClient.js
+16 -1
@@ -827,7 +827,7 @@ function getChunk(response: Response, id: number): SomeChunk<any> {
827 }
828
829 function waitForReference<T>(
830 - referencedChunk: PendingChunk<T> | BlockedChunk<T>,
830 + referencedChunk: SomeChunk<T>,
831 parentObject: Object,
832 key: string,
833 response: Response,
@@ -1003,6 +1003,21 @@ function getOutlinedModel<T>(
1003 let value = chunk.value;
1004 for (let i = 1; i < path.length; i++) {
1005 value = value[path[i]];
1006 + if (value.$$typeof === REACT_LAZY_TYPE) {
1007 + const referencedChunk: SomeChunk<any> = value._payload;
1008 + if (referencedChunk.status === INITIALIZED) {
1009 + value = referencedChunk.value;
1010 + } else {
1011 + return waitForReference(
1012 + referencedChunk,
1013 + parentObject,
1014 + key,
1015 + response,
1016 + map,
1017 + path.slice(i),
1018 + );
1019 + }
1020 + }
1021 }
1022 const chunkValue = map(response, value);
1023 if (__DEV__ && chunk._debugInfo) {
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+69
@@ -456,6 +456,75 @@ describe('ReactFlightDOMBrowser', () => {
456 expect(container.innerHTML).toBe('{}');
457 });
458
459 + it('should resolve deduped objects in blocked models referencing other blocked models with blocked references', async () => {
460 + let resolveFooClientComponentChunk;
461 + let resolveBarClientComponentChunk;
462 +
463 + function PassthroughServerComponent({children}) {
464 + return children;
465 + }
466 +
467 + const FooClient = clientExports(
468 + function FooClient({children}) {
469 + return JSON.stringify(children);
470 + },
471 + '1',
472 + '/foo.js',
473 + new Promise(resolve => (resolveFooClientComponentChunk = resolve)),
474 + );
475 +
476 + const BarClient = clientExports(
477 + function BarClient() {
478 + return 'not used';
479 + },
480 + '2',
481 + '/bar.js',
482 + new Promise(resolve => (resolveBarClientComponentChunk = resolve)),
483 + );
484 +
485 + const shared = {foo: 1};
486 +
487 + function Server() {
488 + return (
489 + <>
490 + <PassthroughServerComponent>
491 + <FooClient key="first" bar={BarClient}>
492 + {shared}
493 + </FooClient>
494 + </PassthroughServerComponent>
495 + <FooClient key="second" bar={BarClient}>
496 + {shared}
497 + </FooClient>
498 + </>
499 + );
500 + }
501 +
502 + const stream = await serverAct(() =>
503 + ReactServerDOMServer.renderToReadableStream(<Server />, webpackMap),
504 + );
505 +
506 + function ClientRoot({response}) {
507 + return use(response);
508 + }
509 +
510 + const response = ReactServerDOMClient.createFromReadableStream(stream);
511 + const container = document.createElement('div');
512 + const root = ReactDOMClient.createRoot(container);
513 +
514 + await act(() => {
515 + root.render(<ClientRoot response={response} />);
516 + });
517 +
518 + expect(container.innerHTML).toBe('');
519 +
520 + await act(() => {
521 + resolveFooClientComponentChunk();
522 + resolveBarClientComponentChunk();
523 + });
524 +
525 + expect(container.innerHTML).toBe('{"foo":1}{"foo":1}');
526 + });
527 +
528 it('should progressively reveal server components', async () => {
529 let reportedErrors = [];
530