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>
This commit is contained in:
Fran Dios 2022-01-25 02:52:51 +09:00 committed by GitHub
parent e28a0db224
commit 529dc3ce84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 187 additions and 2 deletions

View File

@ -338,4 +338,188 @@ describe('ReactDOMFizzServer', () => {
expect(output.result).toContain('Loading');
expect(isCompleteCalls).toBe(1);
});
// @gate experimental
it('should be able to get context value when promise resolves', async () => {
class DelayClient {
get() {
if (this.resolved) return this.resolved;
if (this.pending) return this.pending;
return (this.pending = new Promise(resolve => {
setTimeout(() => {
delete this.pending;
this.resolved = 'OK';
resolve();
}, 500);
}));
}
}
const DelayContext = React.createContext(undefined);
const Component = () => {
const client = React.useContext(DelayContext);
if (!client) {
return 'context not found.';
}
const result = client.get();
if (typeof result === 'string') {
return result;
}
throw result;
};
const client = new DelayClient();
const {writable, output, completed} = getTestWritable();
ReactDOMFizzServer.renderToPipeableStream(
<DelayContext.Provider value={client}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>,
).pipe(writable);
jest.runAllTimers();
expect(output.error).toBe(undefined);
expect(output.result).toContain('loading');
await completed;
expect(output.error).toBe(undefined);
expect(output.result).not.toContain('context never found');
expect(output.result).toContain('OK');
});
// @gate experimental
it('should be able to get context value when calls renderToPipeableStream twice at the same time', async () => {
class DelayClient {
get() {
if (this.resolved) return this.resolved;
if (this.pending) return this.pending;
return (this.pending = new Promise(resolve => {
setTimeout(() => {
delete this.pending;
this.resolved = 'OK';
resolve();
}, 500);
}));
}
}
const DelayContext = React.createContext(undefined);
const Component = () => {
const client = React.useContext(DelayContext);
if (!client) {
return 'context never found';
}
const result = client.get();
if (typeof result === 'string') {
return result;
}
throw result;
};
const client0 = new DelayClient();
const {
writable: writable0,
output: output0,
completed: completed0,
} = getTestWritable();
ReactDOMFizzServer.renderToPipeableStream(
<DelayContext.Provider value={client0}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>,
).pipe(writable0);
const client1 = new DelayClient();
const {
writable: writable1,
output: output1,
completed: completed1,
} = getTestWritable();
ReactDOMFizzServer.renderToPipeableStream(
<DelayContext.Provider value={client1}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>,
).pipe(writable1);
jest.runAllTimers();
expect(output0.error).toBe(undefined);
expect(output0.result).toContain('loading');
expect(output1.error).toBe(undefined);
expect(output1.result).toContain('loading');
await Promise.all([completed0, completed1]);
expect(output0.error).toBe(undefined);
expect(output0.result).not.toContain('context never found');
expect(output0.result).toContain('OK');
expect(output1.error).toBe(undefined);
expect(output1.result).not.toContain('context never found');
expect(output1.result).toContain('OK');
});
// @gate experimental
it('should be able to pop context after suspending', async () => {
class DelayClient {
get() {
if (this.resolved) return this.resolved;
if (this.pending) return this.pending;
return (this.pending = new Promise(resolve => {
setTimeout(() => {
delete this.pending;
this.resolved = 'OK';
resolve();
}, 500);
}));
}
}
const DelayContext = React.createContext(undefined);
const Component = () => {
const client = React.useContext(DelayContext);
if (!client) {
return 'context not found.';
}
const result = client.get();
if (typeof result === 'string') {
return result;
}
throw result;
};
const client = new DelayClient();
const {writable, output, completed} = getTestWritable();
ReactDOMFizzServer.renderToPipeableStream(
<>
<DelayContext.Provider value={client}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>
<DelayContext.Provider value={client}>
<Suspense fallback="loading">
<Component />
</Suspense>
</DelayContext.Provider>
</>,
).pipe(writable);
jest.runAllTimers();
expect(output.error).toBe(undefined);
expect(output.result).toContain('loading');
await completed;
expect(output.error).toBe(undefined);
expect(output.result).not.toContain('context never found');
expect(output.result).toContain('OK');
});
});

View File

@ -78,9 +78,10 @@ function popToNearestCommonAncestor(
}
popToNearestCommonAncestor(parentPrev, parentNext);
// On the way back, we push the new ones that weren't common.
pushNode(next);
}
// On the way back, we push the new ones that weren't common.
pushNode(next);
}
}