Skip to content

Commit f193213

Browse files
tyao1acdlite
andauthored
Add a regression test for an infinite suspense + Fix (#27703)
Add a regression test for the [minimal repro](https://codesandbox.io/s/react-18-suspense-state-never-resolving-bug-hmlny5?file=/src/App.js) from @kassens And includes the fix from @acdlite: > This is another place we special-case Retry lanes to opt them out of expiration. The reason is we rely on time slicing to unwrap uncached promises (i.e. async functions during render). Since that ability is still experimental, and enableRetryLaneExpiration is Meta-only, we can remove the special case when enableRetryLaneExpiration is on, for now. --------- Co-authored-by: Andrew Clark <git@andrewclark.io>
1 parent a3aae7f commit f193213

File tree

2 files changed

+75
-1
lines changed

2 files changed

+75
-1
lines changed

packages/react-reconciler/src/ReactFiberLane.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,9 @@ export function markStarvedLanesAsExpired(
423423
// We exclude retry lanes because those must always be time sliced, in order
424424
// to unwrap uncached promises.
425425
// TODO: Write a test for this
426-
let lanes = pendingLanes & ~RetryLanes;
426+
let lanes = enableRetryLaneExpiration
427+
? pendingLanes
428+
: pendingLanes & ~RetryLanes;
427429
while (lanes > 0) {
428430
const index = pickArbitraryLaneIndex(lanes);
429431
const lane = 1 << index;

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

+72
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ let Scheduler;
55
let act;
66
let waitFor;
77
let waitForAll;
8+
let waitForMicrotasks;
89
let assertLog;
910
let waitForPaint;
1011
let Suspense;
@@ -29,6 +30,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2930
waitFor = InternalTestUtils.waitFor;
3031
waitForAll = InternalTestUtils.waitForAll;
3132
waitForPaint = InternalTestUtils.waitForPaint;
33+
waitForMicrotasks = InternalTestUtils.waitForMicrotasks;
3234
assertLog = InternalTestUtils.assertLog;
3335

3436
getCacheForType = React.unstable_getCacheForType;
@@ -4008,4 +4010,74 @@ describe('ReactSuspenseWithNoopRenderer', () => {
40084010
);
40094011
},
40104012
);
4013+
4014+
// @gate enableLegacyCache && enableRetryLaneExpiration
4015+
it('recurring updates in siblings should not block expensive content in suspense boundary from committing', async () => {
4016+
const {useState} = React;
4017+
4018+
let setText;
4019+
function UpdatingText() {
4020+
const [text, _setText] = useState('1');
4021+
setText = _setText;
4022+
return <Text text={text} />;
4023+
}
4024+
4025+
function ExpensiveText({text, ms}) {
4026+
Scheduler.log(text);
4027+
Scheduler.unstable_advanceTime(ms);
4028+
return <span prop={text} />;
4029+
}
4030+
4031+
function App() {
4032+
return (
4033+
<>
4034+
<UpdatingText />
4035+
<Suspense fallback={<Text text="Loading..." />}>
4036+
<AsyncText text="Async" />
4037+
<ExpensiveText text="A" ms={1000} />
4038+
<ExpensiveText text="B" ms={3999} />
4039+
<ExpensiveText text="C" ms={100000} />
4040+
</Suspense>
4041+
</>
4042+
);
4043+
}
4044+
4045+
const root = ReactNoop.createRoot();
4046+
root.render(<App />);
4047+
await waitForAll(['1', 'Suspend! [Async]', 'Loading...']);
4048+
expect(root).toMatchRenderedOutput(
4049+
<>
4050+
<span prop="1" />
4051+
<span prop="Loading..." />
4052+
</>,
4053+
);
4054+
4055+
await resolveText('Async');
4056+
expect(root).toMatchRenderedOutput(
4057+
<>
4058+
<span prop="1" />
4059+
<span prop="Loading..." />
4060+
</>,
4061+
);
4062+
4063+
await waitFor(['Async', 'A', 'B']);
4064+
ReactNoop.expire(100000);
4065+
await advanceTimers(100000);
4066+
setText('2');
4067+
await waitForPaint(['2']);
4068+
4069+
await waitForMicrotasks();
4070+
Scheduler.unstable_flushNumberOfYields(1);
4071+
assertLog(['Async', 'A', 'B', 'C']);
4072+
4073+
expect(root).toMatchRenderedOutput(
4074+
<>
4075+
<span prop="2" />
4076+
<span prop="Async" />
4077+
<span prop="A" />
4078+
<span prop="B" />
4079+
<span prop="C" />
4080+
</>,
4081+
);
4082+
});
40114083
});

0 commit comments

Comments
 (0)