Skip to content

Commit 9cd52b2

Browse files
authored
Restore context after an error happens (#21341)
Typically we don't need to restore the context here because we assume that we'll terminate the rest of the subtree so we don't need the correct context since we're not rendering any siblings. However, after a nested suspense boundary we need to restore the context. The boundary could do this but since we're already doing this in the suspense branch of renderNode, we might as well do it in the error case which isn't very perf sensitive anyway.
1 parent ad09175 commit 9cd52b2

File tree

2 files changed

+74
-0
lines changed

2 files changed

+74
-0
lines changed

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,73 @@ describe('ReactDOMFizzServer', () => {
796796
);
797797
});
798798

799+
// @gate experimental
800+
it('should recover the outer context when an error happens inside a provider', async () => {
801+
const ContextA = React.createContext('A0');
802+
const ContextB = React.createContext('B0');
803+
804+
function PrintA() {
805+
return (
806+
<ContextA.Consumer>{value => <Text text={value} />}</ContextA.Consumer>
807+
);
808+
}
809+
810+
class PrintB extends React.Component {
811+
static contextType = ContextB;
812+
render() {
813+
return <Text text={this.context} />;
814+
}
815+
}
816+
817+
function Throws() {
818+
const value = React.useContext(ContextA);
819+
throw new Error(value);
820+
}
821+
822+
const loggedErrors = [];
823+
await act(async () => {
824+
const {startWriting} = ReactDOMFizzServer.pipeToNodeWritable(
825+
<div>
826+
<PrintA />
827+
<div>
828+
<ContextA.Provider value="A0.1">
829+
<Suspense
830+
fallback={
831+
<b>
832+
<Text text="Loading..." />
833+
</b>
834+
}>
835+
<ContextA.Provider value="A0.1.1">
836+
<Throws />
837+
</ContextA.Provider>
838+
</Suspense>
839+
<PrintB />
840+
</ContextA.Provider>
841+
</div>
842+
<PrintA />
843+
</div>,
844+
writable,
845+
{
846+
onError(x) {
847+
loggedErrors.push(x);
848+
},
849+
},
850+
);
851+
startWriting();
852+
});
853+
expect(loggedErrors.length).toBe(1);
854+
expect(loggedErrors[0].message).toEqual('A0.1.1');
855+
expect(getVisibleChildren(container)).toEqual(
856+
<div>
857+
A0
858+
<div>
859+
<b>Loading...</b>B0
860+
</div>
861+
A0
862+
</div>,
863+
);
864+
});
865+
799866
// @gate experimental
800867
it('client renders a boundary if it errors before finishing the fallback', async () => {
801868
function App({isClient}) {

packages/react-server/src/ReactFizzServer.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,13 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
11741174
// Restore all active ReactContexts to what they were before.
11751175
switchContext(previousContext);
11761176
} else {
1177+
// Restore the context. We assume that this will be restored by the inner
1178+
// functions in case nothing throws so we don't use "finally" here.
1179+
task.blockedSegment.formatContext = previousFormatContext;
1180+
task.legacyContext = previousLegacyContext;
1181+
task.context = previousContext;
1182+
// Restore all active ReactContexts to what they were before.
1183+
switchContext(previousContext);
11771184
// We assume that we don't need the correct context.
11781185
// Let's terminate the rest of the tree and don't render any siblings.
11791186
throw x;

0 commit comments

Comments
 (0)