From 3722dc7a5a764a38c69b6baf1d665854abc374c1 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Wed, 24 Jul 2024 14:46:48 +0200 Subject: [PATCH 1/4] Add a failing test for resolving blocked references of lazy elements The bug was uncovered after updating React in Next.js in https://github.com/vercel/next.js/pull/66711. The test fails with: ``` 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) ``` --- .../__tests__/ReactFlightDOMBrowser-test.js | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index 49f2823c8b387..def347e83a18b 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -456,6 +456,75 @@ describe('ReactFlightDOMBrowser', () => { expect(container.innerHTML).toBe('{}'); }); + it('should resolve deduped objects in blocked models referencing other blocked models with blocked references', async () => { + let resolveFooClientComponentChunk; + let resolveBarClientComponentChunk; + + function PassthroughServerComponent({children}) { + return children; + } + + const FooClient = clientExports( + function FooClient({children}) { + return JSON.stringify(children); + }, + '1', + '/foo.js', + new Promise(resolve => (resolveFooClientComponentChunk = resolve)), + ); + + const BarClient = clientExports( + function BarClient() { + return 'not used'; + }, + '2', + '/bar.js', + new Promise(resolve => (resolveBarClientComponentChunk = resolve)), + ); + + const shared = {foo: 1}; + + function Server() { + return ( + <> + + + {shared} + + + + {shared} + + + ); + } + + const stream = await serverAct(() => + ReactServerDOMServer.renderToReadableStream(, webpackMap), + ); + + function ClientRoot({response}) { + return use(response); + } + + const response = ReactServerDOMClient.createFromReadableStream(stream); + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render(); + }); + + expect(container.innerHTML).toBe(''); + + await act(() => { + resolveFooClientComponentChunk(); + resolveBarClientComponentChunk(); + }); + + expect(container.innerHTML).toBe('{"foo":1}{"foo":1}'); + }); + it('should progressively reveal server components', async () => { let reportedErrors = []; From d994fd21db957399b87072b615b2d3e0a9bb3d6a Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Wed, 24 Jul 2024 22:39:10 +0200 Subject: [PATCH 2/4] Fix resolving of references to deduped props in lazy elements --- packages/react-client/src/ReactFlightClient.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index ac2d3218ed825..d1f1c8884a465 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -1003,6 +1003,16 @@ function getOutlinedModel( let value = chunk.value; for (let i = 1; i < path.length; i++) { value = value[path[i]]; + if (value.$$typeof === REACT_LAZY_TYPE) { + return waitForReference( + value._payload, + parentObject, + key, + response, + map, + path.slice(i), + ); + } } const chunkValue = map(response, value); if (__DEV__ && chunk._debugInfo) { From 3f6204203c71ac052dd7793c66de396897363393 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Wed, 24 Jul 2024 23:26:26 +0200 Subject: [PATCH 3/4] Don't call `waitForReference` for initialized chunks --- .../react-client/src/ReactFlightClient.js | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index d1f1c8884a465..dedbfcc255dcd 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -1004,14 +1004,22 @@ function getOutlinedModel( for (let i = 1; i < path.length; i++) { value = value[path[i]]; if (value.$$typeof === REACT_LAZY_TYPE) { - return waitForReference( - value._payload, - parentObject, - key, - response, - map, - path.slice(i), - ); + const referencedChunk: SomeChunk = value._payload; + if (referencedChunk.status === INITIALIZED) { + value = referencedChunk.value; + } else if ( + referencedChunk.status === BLOCKED || + referencedChunk.status === PENDING + ) { + return waitForReference( + referencedChunk, + parentObject, + key, + response, + map, + path.slice(i), + ); + } } } const chunkValue = map(response, value); From f0aee7ce528b6f66418c4d198d2fe0e5eb25e285 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 25 Jul 2024 00:19:00 +0200 Subject: [PATCH 4/4] Allow `SomeChunk` in `waitForReference` --- packages/react-client/src/ReactFlightClient.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index dedbfcc255dcd..78e883df7b33b 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -827,7 +827,7 @@ function getChunk(response: Response, id: number): SomeChunk { } function waitForReference( - referencedChunk: PendingChunk | BlockedChunk, + referencedChunk: SomeChunk, parentObject: Object, key: string, response: Response, @@ -1007,10 +1007,7 @@ function getOutlinedModel( const referencedChunk: SomeChunk = value._payload; if (referencedChunk.status === INITIALIZED) { value = referencedChunk.value; - } else if ( - referencedChunk.status === BLOCKED || - referencedChunk.status === PENDING - ) { + } else { return waitForReference( referencedChunk, parentObject,