Skip to content

Commit d634ba7

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents e624f42 + c0d218f commit d634ba7

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5040,6 +5040,13 @@ function pingSuspendedRoot(
50405040
// the special internal exception that we use to interrupt the stack for
50415041
// selective hydration. That was temporarily reverted but we once we add
50425042
// it back we can use it here.
5043+
//
5044+
// In the meantime, record the pinged lanes so markRootSuspended won't
5045+
// mark them as suspended, allowing a retry.
5046+
workInProgressRootPingedLanes = mergeLanes(
5047+
workInProgressRootPingedLanes,
5048+
pingedLanes,
5049+
);
50435050
}
50445051
} else {
50455052
// Even though we can't restart right now, we might get an

packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,4 +1097,56 @@ describe('ReactDeferredValue', () => {
10971097
expect(root).toMatchRenderedOutput(<div>B</div>);
10981098
},
10991099
);
1100+
1101+
// Regression test for https://github.com/facebook/react/issues/35821
1102+
it('deferred value catches up when a suspension is resolved during the same render', async () => {
1103+
let setValue;
1104+
function App() {
1105+
const [value, _setValue] = useState('initial');
1106+
setValue = _setValue;
1107+
const deferred = useDeferredValue(value);
1108+
return (
1109+
<Suspense fallback={<Text text="Loading..." />}>
1110+
<AsyncText text={'A:' + deferred} />
1111+
<Sibling text={deferred} />
1112+
</Suspense>
1113+
);
1114+
}
1115+
1116+
function Sibling({text}) {
1117+
if (text !== 'initial') {
1118+
// Resolve A during this render, simulating data arriving while
1119+
// a render is already in progress.
1120+
resolveText('A:' + text);
1121+
}
1122+
readText('B:' + text);
1123+
Scheduler.log('B: ' + text);
1124+
return text;
1125+
}
1126+
1127+
const root = ReactNoop.createRoot();
1128+
1129+
resolveText('A:initial');
1130+
resolveText('B:initial');
1131+
await act(() => root.render(<App />));
1132+
assertLog(['A:initial', 'B: initial']);
1133+
1134+
// Pre-resolve B so the sibling won't suspend on retry.
1135+
resolveText('B:updated');
1136+
1137+
await act(() => setValue('updated'));
1138+
assertLog([
1139+
// Sync render defers the value.
1140+
'A:initial',
1141+
'B: initial',
1142+
// Deferred render: A suspends, then Sibling resolves A mid-render.
1143+
'Suspend! [A:updated]',
1144+
'B: updated',
1145+
'Loading...',
1146+
// React retries and the deferred value catches up.
1147+
'A:updated',
1148+
'B: updated',
1149+
]);
1150+
expect(root).toMatchRenderedOutput('A:updatedupdated');
1151+
});
11001152
});

packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4054,11 +4054,20 @@ describe('ReactSuspenseWithNoopRenderer', () => {
40544054
// microtask). But this test shows an example where that's not the case.
40554055
//
40564056
// The fix was to check if we're in the render phase before calling
4057-
// `prepareFreshStack`.
4057+
// `prepareFreshStack`. The synchronous ping is instead recorded so the
4058+
// lane can be retried.
40584059
await act(() => {
40594060
startTransition(() => root.render(<App showMore={true} />));
40604061
});
4061-
assertLog(['Suspend! [A]', 'Loading A...', 'Loading B...']);
4062+
assertLog([
4063+
'Suspend! [A]',
4064+
'Loading A...',
4065+
'Loading B...',
4066+
// The synchronous ping was recorded, so B retries and renders.
4067+
'Suspend! [A]',
4068+
'Loading A...',
4069+
'B',
4070+
]);
40624071
expect(root).toMatchRenderedOutput(<div />);
40634072
},
40644073
);

0 commit comments

Comments
 (0)