@samitouri / QOS-React / commits / 529dc3ce84

Fix context providers in SSR when handling multiple requests (#23171)

* add failing test for renderToPipeableStream * Fix context providers in SSR when handling multiple requests. Closes #23089 * Add sibling regression test Co-authored-by: zhuyi01 <zhuyi01@ke.com> Co-authored-by: Dan Abramov <dan.abramov@me.com>

Fran Dios committed Jan 25, 2022 at 02:52 UTC 529dc3ce84f0efe99a8be33ff453c09d5801d5ca
2 files changed +187 -2
packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
+184
@@ -338,4 +338,188 @@ describe('ReactDOMFizzServer', () => {
338 expect(output.result).toContain('Loading');
339 expect(isCompleteCalls).toBe(1);
340 });
341 +
342 + // @gate experimental
343 + it('should be able to get context value when promise resolves', async () => {
344 + class DelayClient {
345 + get() {
346 + if (this.resolved) return this.resolved;
347 + if (this.pending) return this.pending;
348 + return (this.pending = new Promise(resolve => {
349 + setTimeout(() => {
350 + delete this.pending;
351 + this.resolved = 'OK';
352 + resolve();
353 + }, 500);
354 + }));
355 + }
356 + }
357 +
358 + const DelayContext = React.createContext(undefined);
359 + const Component = () => {
360 + const client = React.useContext(DelayContext);
361 + if (!client) {
362 + return 'context not found.';
363 + }
364 + const result = client.get();
365 + if (typeof result === 'string') {
366 + return result;
367 + }
368 + throw result;
369 + };
370 +
371 + const client = new DelayClient();
372 + const {writable, output, completed} = getTestWritable();
373 + ReactDOMFizzServer.renderToPipeableStream(
374 + <DelayContext.Provider value={client}>
375 + <Suspense fallback="loading">
376 + <Component />
377 + </Suspense>
378 + </DelayContext.Provider>,
379 + ).pipe(writable);
380 +
381 + jest.runAllTimers();
382 +
383 + expect(output.error).toBe(undefined);
384 + expect(output.result).toContain('loading');
385 +
386 + await completed;
387 +
388 + expect(output.error).toBe(undefined);
389 + expect(output.result).not.toContain('context never found');
390 + expect(output.result).toContain('OK');
391 + });
392 +
393 + // @gate experimental
394 + it('should be able to get context value when calls renderToPipeableStream twice at the same time', async () => {
395 + class DelayClient {
396 + get() {
397 + if (this.resolved) return this.resolved;
398 + if (this.pending) return this.pending;
399 + return (this.pending = new Promise(resolve => {
400 + setTimeout(() => {
401 + delete this.pending;
402 + this.resolved = 'OK';
403 + resolve();
404 + }, 500);
405 + }));
406 + }
407 + }
408 + const DelayContext = React.createContext(undefined);
409 + const Component = () => {
410 + const client = React.useContext(DelayContext);
411 + if (!client) {
412 + return 'context never found';
413 + }
414 + const result = client.get();
415 + if (typeof result === 'string') {
416 + return result;
417 + }
418 + throw result;
419 + };
420 +
421 + const client0 = new DelayClient();
422 + const {
423 + writable: writable0,
424 + output: output0,
425 + completed: completed0,
426 + } = getTestWritable();
427 + ReactDOMFizzServer.renderToPipeableStream(
428 + <DelayContext.Provider value={client0}>
429 + <Suspense fallback="loading">
430 + <Component />
431 + </Suspense>
432 + </DelayContext.Provider>,
433 + ).pipe(writable0);
434 +
435 + const client1 = new DelayClient();
436 + const {
437 + writable: writable1,
438 + output: output1,
439 + completed: completed1,
440 + } = getTestWritable();
441 + ReactDOMFizzServer.renderToPipeableStream(
442 + <DelayContext.Provider value={client1}>
443 + <Suspense fallback="loading">
444 + <Component />
445 + </Suspense>
446 + </DelayContext.Provider>,
447 + ).pipe(writable1);
448 +
449 + jest.runAllTimers();
450 +
451 + expect(output0.error).toBe(undefined);
452 + expect(output0.result).toContain('loading');
453 +
454 + expect(output1.error).toBe(undefined);
455 + expect(output1.result).toContain('loading');
456 +
457 + await Promise.all([completed0, completed1]);
458 +
459 + expect(output0.error).toBe(undefined);
460 + expect(output0.result).not.toContain('context never found');
461 + expect(output0.result).toContain('OK');
462 +
463 + expect(output1.error).toBe(undefined);
464 + expect(output1.result).not.toContain('context never found');
465 + expect(output1.result).toContain('OK');
466 + });
467 +
468 + // @gate experimental
469 + it('should be able to pop context after suspending', async () => {
470 + class DelayClient {
471 + get() {
472 + if (this.resolved) return this.resolved;
473 + if (this.pending) return this.pending;
474 + return (this.pending = new Promise(resolve => {
475 + setTimeout(() => {
476 + delete this.pending;
477 + this.resolved = 'OK';
478 + resolve();
479 + }, 500);
480 + }));
481 + }
482 + }
483 +
484 + const DelayContext = React.createContext(undefined);
485 + const Component = () => {
486 + const client = React.useContext(DelayContext);
487 + if (!client) {
488 + return 'context not found.';
489 + }
490 + const result = client.get();
491 + if (typeof result === 'string') {
492 + return result;
493 + }
494 + throw result;
495 + };
496 +
497 + const client = new DelayClient();
498 + const {writable, output, completed} = getTestWritable();
499 + ReactDOMFizzServer.renderToPipeableStream(
500 + <>
501 + <DelayContext.Provider value={client}>
502 + <Suspense fallback="loading">
503 + <Component />
504 + </Suspense>
505 + </DelayContext.Provider>
506 + <DelayContext.Provider value={client}>
507 + <Suspense fallback="loading">
508 + <Component />
509 + </Suspense>
510 + </DelayContext.Provider>
511 + </>,
512 + ).pipe(writable);
513 +
514 + jest.runAllTimers();
515 +
516 + expect(output.error).toBe(undefined);
517 + expect(output.result).toContain('loading');
518 +
519 + await completed;
520 +
521 + expect(output.error).toBe(undefined);
522 + expect(output.result).not.toContain('context never found');
523 + expect(output.result).toContain('OK');
524 + });
525 });
packages/react-server/src/ReactFizzNewContext.js
+3 -2
@@ -78,9 +78,10 @@ function popToNearestCommonAncestor(
78 }
79
80 popToNearestCommonAncestor(parentPrev, parentNext);
81 - // On the way back, we push the new ones that weren't common.
82 - pushNode(next);
81 }
82 +
83 + // On the way back, we push the new ones that weren't common.
84 + pushNode(next);
85 }
86 }
87