Skip to content

Commit 64223fe

Browse files
authored
Fix: Multiple hydration errors in same render (#23273)
I made a minor mistake in the original onRecoverableError PR that only surfaces if there are hydration errors in two different Suspense boundaries in the same render. This fixes it and adds a unit test.
1 parent efd8f64 commit 64223fe

File tree

3 files changed

+70
-6
lines changed

3 files changed

+70
-6
lines changed

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,4 +2191,68 @@ describe('ReactDOMFizzServer', () => {
21912191
// UI looks normal
21922192
expect(container.textContent).toEqual('AB');
21932193
});
2194+
2195+
// @gate experimental
2196+
it('logs multiple hydration errors in the same render', async () => {
2197+
let isClient = false;
2198+
2199+
function subscribe() {
2200+
return () => {};
2201+
}
2202+
function getClientSnapshot() {
2203+
return 'Yay!';
2204+
}
2205+
function getServerSnapshot() {
2206+
if (isClient) {
2207+
throw new Error('Hydration error');
2208+
}
2209+
return 'Yay!';
2210+
}
2211+
2212+
function Child({label}) {
2213+
// This will throw during client hydration. Only reason to use
2214+
// useSyncExternalStore in this test is because getServerSnapshot has the
2215+
// ability to observe whether we're hydrating.
2216+
useSyncExternalStore(subscribe, getClientSnapshot, getServerSnapshot);
2217+
Scheduler.unstable_yieldValue(label);
2218+
return label;
2219+
}
2220+
2221+
function App() {
2222+
return (
2223+
<>
2224+
<Suspense fallback="Loading...">
2225+
<Child label="A" />
2226+
</Suspense>
2227+
<Suspense fallback="Loading...">
2228+
<Child label="B" />
2229+
</Suspense>
2230+
</>
2231+
);
2232+
}
2233+
2234+
await act(async () => {
2235+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
2236+
pipe(writable);
2237+
});
2238+
expect(Scheduler).toHaveYielded(['A', 'B']);
2239+
2240+
// Hydrate the tree. Child will throw during hydration, but not when it
2241+
// falls back to client rendering.
2242+
isClient = true;
2243+
ReactDOM.hydrateRoot(container, <App />, {
2244+
onRecoverableError(error) {
2245+
Scheduler.unstable_yieldValue(
2246+
'Logged recoverable error: ' + error.message,
2247+
);
2248+
},
2249+
});
2250+
2251+
expect(Scheduler).toFlushAndYield([
2252+
'A',
2253+
'B',
2254+
'Logged recoverable error: Hydration error',
2255+
'Logged recoverable error: Hydration error',
2256+
]);
2257+
});
21942258
});

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -920,11 +920,11 @@ function recoverFromConcurrentError(root, errorRetryLanes) {
920920
}
921921

922922
export function queueRecoverableErrors(errors: Array<mixed>) {
923-
if (workInProgressRootConcurrentErrors === null) {
923+
if (workInProgressRootRecoverableErrors === null) {
924924
workInProgressRootRecoverableErrors = errors;
925925
} else {
926-
workInProgressRootConcurrentErrors = workInProgressRootConcurrentErrors.push.apply(
927-
workInProgressRootConcurrentErrors,
926+
workInProgressRootRecoverableErrors.push.apply(
927+
workInProgressRootRecoverableErrors,
928928
errors,
929929
);
930930
}

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -920,11 +920,11 @@ function recoverFromConcurrentError(root, errorRetryLanes) {
920920
}
921921

922922
export function queueRecoverableErrors(errors: Array<mixed>) {
923-
if (workInProgressRootConcurrentErrors === null) {
923+
if (workInProgressRootRecoverableErrors === null) {
924924
workInProgressRootRecoverableErrors = errors;
925925
} else {
926-
workInProgressRootConcurrentErrors = workInProgressRootConcurrentErrors.push.apply(
927-
workInProgressRootConcurrentErrors,
926+
workInProgressRootRecoverableErrors.push.apply(
927+
workInProgressRootRecoverableErrors,
928928
errors,
929929
);
930930
}

0 commit comments

Comments
 (0)