Skip to content

Commit

Permalink
Fix resolving of references to deduped props in lazy elements (#30441)
Browse files Browse the repository at this point in the history
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
vercel/next.js#66711.
  • Loading branch information
unstubbable authored Jul 24, 2024
1 parent 933b737 commit 7600225
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 1 deletion.
17 changes: 16 additions & 1 deletion packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ function getChunk(response: Response, id: number): SomeChunk<any> {
}

function waitForReference<T>(
referencedChunk: PendingChunk<T> | BlockedChunk<T>,
referencedChunk: SomeChunk<T>,
parentObject: Object,
key: string,
response: Response,
Expand Down Expand Up @@ -1003,6 +1003,21 @@ function getOutlinedModel<T>(
let value = chunk.value;
for (let i = 1; i < path.length; i++) {
value = value[path[i]];
if (value.$$typeof === REACT_LAZY_TYPE) {
const referencedChunk: SomeChunk<any> = value._payload;
if (referencedChunk.status === INITIALIZED) {
value = referencedChunk.value;
} else {
return waitForReference(
referencedChunk,
parentObject,
key,
response,
map,
path.slice(i),
);
}
}
}
const chunkValue = map(response, value);
if (__DEV__ && chunk._debugInfo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<>
<PassthroughServerComponent>
<FooClient key="first" bar={BarClient}>
{shared}
</FooClient>
</PassthroughServerComponent>
<FooClient key="second" bar={BarClient}>
{shared}
</FooClient>
</>
);
}

const stream = await serverAct(() =>
ReactServerDOMServer.renderToReadableStream(<Server />, 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(<ClientRoot response={response} />);
});

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 = [];

Expand Down

0 comments on commit 7600225

Please sign in to comment.