@samitouri / QOS-React-2 / commits / 8f3c0525f9

[Flight / Flight Reply] Don't clear pending listeners when entering blocked state (#29201)

Fixes #29200 The cyclic state might have added listeners that will still need to be invoked. This happens if we have a cyclic reference AND end up blocked. We have already cleared these before entering the parsing when we enter the CYCLIC state so we they already have the right type. If listeners are added during this phase they should carry over to the blocked state. --------- Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>

Sebastian Markbåge committed May 21, 2024 at 14:12 UTC 8f3c0525f964fc5c75a627a6852f63efce1796b7
3 files changed +49 -4
packages/react-client/src/ReactFlightClient.js
-2
@@ -506,8 +506,6 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
506 // We have to go the BLOCKED state until they're resolved.
507 const blockedChunk: BlockedChunk<T> = (chunk: any);
508 blockedChunk.status = BLOCKED;
509 - blockedChunk.value = null;
510 - blockedChunk.reason = null;
509 } else {
510 const resolveListeners = cyclicChunk.value;
511 const initializedChunk: InitializedChunk<T> = (chunk: any);
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+49
@@ -247,6 +247,55 @@ describe('ReactFlightDOMBrowser', () => {
247 expect(container.innerHTML).toBe('<span>Hello, World!</span>');
248 });
249
250 + it('should resolve deduped objects within the same model root when it is blocked', async () => {
251 + let resolveClientComponentChunk;
252 +
253 + const ClientOuter = clientExports(function ClientOuter({Component, value}) {
254 + return <Component value={value} />;
255 + });
256 +
257 + const ClientInner = clientExports(
258 + function ClientInner({value}) {
259 + return <pre>{JSON.stringify(value)}</pre>;
260 + },
261 + '42',
262 + '/test.js',
263 + new Promise(resolve => (resolveClientComponentChunk = resolve)),
264 + );
265 +
266 + function Server({value}) {
267 + return <ClientOuter Component={ClientInner} value={value} />;
268 + }
269 +
270 + const shared = [1, 2, 3];
271 + const value = [shared, shared];
272 +
273 + const stream = ReactServerDOMServer.renderToReadableStream(
274 + <Server value={value} />,
275 + webpackMap,
276 + );
277 +
278 + function ClientRoot({response}) {
279 + return use(response);
280 + }
281 +
282 + const response = ReactServerDOMClient.createFromReadableStream(stream);
283 + const container = document.createElement('div');
284 + const root = ReactDOMClient.createRoot(container);
285 +
286 + await act(() => {
287 + root.render(<ClientRoot response={response} />);
288 + });
289 +
290 + expect(container.innerHTML).toBe('');
291 +
292 + await act(() => {
293 + resolveClientComponentChunk();
294 + });
295 +
296 + expect(container.innerHTML).toBe('<pre>[[1,2,3],[1,2,3]]</pre>');
297 + });
298 +
299 it('should progressively reveal server components', async () => {
300 let reportedErrors = [];
301
packages/react-server/src/ReactFlightReplyServer.js
-2
@@ -464,8 +464,6 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
464 // We have to go the BLOCKED state until they're resolved.
465 const blockedChunk: BlockedChunk<T> = (chunk: any);
466 blockedChunk.status = BLOCKED;
467 - blockedChunk.value = null;
468 - blockedChunk.reason = null;
467 } else {
468 const resolveListeners = cyclicChunk.value;
469 const initializedChunk: InitializedChunk<T> = (chunk: any);