Skip to content

Commit

Permalink
[Flight] don't overwrite existing chunk listeners in 'wakeChunkIfInit…
Browse files Browse the repository at this point in the history
…ialized' (#29204)

Follow up to #29201. If a chunk
had listeners attached already (e.g. because `.then` was called on the
chunk returned from `createFromReadableStream`),
`wakeChunkIfInitialized` would overwrite any listeners added during
chunk initialization. This caused cyclic [path
references](#28996) within that
chunk to never resolve. Fixed by merging the two arrays of listeners.
  • Loading branch information
lubieowoce committed May 21, 2024
1 parent 0a0a5c0 commit 9b3f909
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 2 deletions.
20 changes: 18 additions & 2 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,24 @@ function wakeChunkIfInitialized<T>(
case PENDING:
case BLOCKED:
case CYCLIC:
chunk.value = resolveListeners;
chunk.reason = rejectListeners;
if (chunk.value) {
for (let i = 0; i < resolveListeners.length; i++) {
chunk.value.push(resolveListeners[i]);
}
} else {
chunk.value = resolveListeners;
}

if (chunk.reason) {
if (rejectListeners) {
for (let i = 0; i < rejectListeners.length; i++) {
chunk.reason.push(rejectListeners[i]);
}
}
} else {
chunk.reason = rejectListeners;
}

break;
case ERRORED:
if (rejectListeners) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,58 @@ describe('ReactFlightDOMBrowser', () => {
expect(container.innerHTML).toBe('<pre>[[1,2,3],[1,2,3]]</pre>');
});

it('should resolve deduped objects within the same model root when it is blocked and there is a listener attached to the root', async () => {
let resolveClientComponentChunk;

const ClientOuter = clientExports(function ClientOuter({Component, value}) {
return <Component value={value} />;
});

const ClientInner = clientExports(
function ClientInner({value}) {
return <pre>{JSON.stringify(value)}</pre>;
},
'42',
'/test.js',
new Promise(resolve => (resolveClientComponentChunk = resolve)),
);

function Server({value}) {
return <ClientOuter Component={ClientInner} value={value} />;
}

const shared = [1, 2, 3];
const value = [shared, shared];

const stream = ReactServerDOMServer.renderToReadableStream(
<Server value={value} />,
webpackMap,
);

function ClientRoot({response}) {
return use(response);
}

const response = ReactServerDOMClient.createFromReadableStream(stream);
// make sure we have a listener so that `resolveModelChunk` initializes the chunk eagerly
response.then(() => {});

const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);

await act(() => {
root.render(<ClientRoot response={response} />);
});

expect(container.innerHTML).toBe('');

await act(() => {
resolveClientComponentChunk();
});

expect(container.innerHTML).toBe('<pre>[[1,2,3],[1,2,3]]</pre>');
});

it('should progressively reveal server components', async () => {
let reportedErrors = [];

Expand Down

0 comments on commit 9b3f909

Please sign in to comment.