@samitouri / QOS-React-1 / commits / 9b3f909cc1

[Flight] don't overwrite existing chunk listeners in 'wakeChunkIfInitialized' (#29204)

Follow up to https://github.com/facebook/react/pull/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](https://github.com/facebook/react/pull/28996) within that chunk to never resolve. Fixed by merging the two arrays of listeners.

Janka Uryga committed May 21, 2024 at 23:04 UTC 9b3f909cc19fd848dcf8cba2cc3750f32053c056
2 files changed +70 -2
packages/react-client/src/ReactFlightClient.js
+18 -2
@@ -320,8 +320,24 @@ function wakeChunkIfInitialized<T>(
320 case PENDING:
321 case BLOCKED:
322 case CYCLIC:
323 - chunk.value = resolveListeners;
324 - chunk.reason = rejectListeners;
323 + if (chunk.value) {
324 + for (let i = 0; i < resolveListeners.length; i++) {
325 + chunk.value.push(resolveListeners[i]);
326 + }
327 + } else {
328 + chunk.value = resolveListeners;
329 + }
330 +
331 + if (chunk.reason) {
332 + if (rejectListeners) {
333 + for (let i = 0; i < rejectListeners.length; i++) {
334 + chunk.reason.push(rejectListeners[i]);
335 + }
336 + }
337 + } else {
338 + chunk.reason = rejectListeners;
339 + }
340 +
341 break;
342 case ERRORED:
343 if (rejectListeners) {
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+52
@@ -296,6 +296,58 @@ describe('ReactFlightDOMBrowser', () => {
296 expect(container.innerHTML).toBe('<pre>[[1,2,3],[1,2,3]]</pre>');
297 });
298
299 + it('should resolve deduped objects within the same model root when it is blocked and there is a listener attached to the root', async () => {
300 + let resolveClientComponentChunk;
301 +
302 + const ClientOuter = clientExports(function ClientOuter({Component, value}) {
303 + return <Component value={value} />;
304 + });
305 +
306 + const ClientInner = clientExports(
307 + function ClientInner({value}) {
308 + return <pre>{JSON.stringify(value)}</pre>;
309 + },
310 + '42',
311 + '/test.js',
312 + new Promise(resolve => (resolveClientComponentChunk = resolve)),
313 + );
314 +
315 + function Server({value}) {
316 + return <ClientOuter Component={ClientInner} value={value} />;
317 + }
318 +
319 + const shared = [1, 2, 3];
320 + const value = [shared, shared];
321 +
322 + const stream = ReactServerDOMServer.renderToReadableStream(
323 + <Server value={value} />,
324 + webpackMap,
325 + );
326 +
327 + function ClientRoot({response}) {
328 + return use(response);
329 + }
330 +
331 + const response = ReactServerDOMClient.createFromReadableStream(stream);
332 + // make sure we have a listener so that `resolveModelChunk` initializes the chunk eagerly
333 + response.then(() => {});
334 +
335 + const container = document.createElement('div');
336 + const root = ReactDOMClient.createRoot(container);
337 +
338 + await act(() => {
339 + root.render(<ClientRoot response={response} />);
340 + });
341 +
342 + expect(container.innerHTML).toBe('');
343 +
344 + await act(() => {
345 + resolveClientComponentChunk();
346 + });
347 +
348 + expect(container.innerHTML).toBe('<pre>[[1,2,3],[1,2,3]]</pre>');
349 + });
350 +
351 it('should progressively reveal server components', async () => {
352 let reportedErrors = [];
353