Skip to content

Commit 529dc3c

Browse files
frandioxzhuyi01gaearon
authored
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>
1 parent e28a0db commit 529dc3c

File tree

2 files changed

+187
-2
lines changed

2 files changed

+187
-2
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js

+184
Original file line numberDiff line numberDiff line change
@@ -338,4 +338,188 @@ describe('ReactDOMFizzServer', () => {
338338
expect(output.result).toContain('Loading');
339339
expect(isCompleteCalls).toBe(1);
340340
});
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+
});
341525
});

packages/react-server/src/ReactFizzNewContext.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,10 @@ function popToNearestCommonAncestor(
7878
}
7979

8080
popToNearestCommonAncestor(parentPrev, parentNext);
81-
// On the way back, we push the new ones that weren't common.
82-
pushNode(next);
8381
}
82+
83+
// On the way back, we push the new ones that weren't common.
84+
pushNode(next);
8485
}
8586
}
8687

0 commit comments

Comments
 (0)