Skip to content

Commit d1270e9

Browse files
gaearonacdlite
authored andcommitted
Add repro case for #18486
1 parent 1bcf7d2 commit d1270e9

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed

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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3758,4 +3758,119 @@ describe('ReactSuspenseWithNoopRenderer', () => {
37583758
</>,
37593759
);
37603760
});
3761+
3762+
// Regression: https://github.com/facebook/react/issues/18486
3763+
it.experimental(
3764+
'does not get stuck in pending state with render phase updates',
3765+
async () => {
3766+
let setTextWithTransition;
3767+
3768+
function App() {
3769+
const [startTransition, isPending] = React.useTransition({
3770+
timeoutMs: 30000,
3771+
});
3772+
const [text, setText] = React.useState('');
3773+
const [mirror, setMirror] = React.useState('');
3774+
3775+
if (text !== mirror) {
3776+
// Render phase update was needed to repro the bug.
3777+
setMirror(text);
3778+
}
3779+
3780+
setTextWithTransition = value => {
3781+
startTransition(() => {
3782+
setText(value);
3783+
});
3784+
};
3785+
3786+
return (
3787+
<>
3788+
{isPending ? <Text text="Pending..." /> : null}
3789+
{text !== '' ? <AsyncText text={text} /> : <Text text={text} />}
3790+
</>
3791+
);
3792+
}
3793+
3794+
function Root() {
3795+
return (
3796+
<Suspense fallback={<Text text="Loading..." />}>
3797+
<App />
3798+
</Suspense>
3799+
);
3800+
}
3801+
3802+
const root = ReactNoop.createRoot();
3803+
await ReactNoop.act(async () => {
3804+
root.render(<Root />);
3805+
});
3806+
expect(Scheduler).toHaveYielded(['']);
3807+
expect(root).toMatchRenderedOutput(<span prop="" />);
3808+
3809+
// Update to "a". That will suspend.
3810+
await ReactNoop.act(async () => {
3811+
setTextWithTransition('a');
3812+
// Let it expire. This is important for the repro.
3813+
Scheduler.unstable_advanceTime(1000);
3814+
expect(Scheduler).toFlushAndYield([
3815+
'Pending...',
3816+
'',
3817+
'Suspend! [a]',
3818+
'Loading...',
3819+
]);
3820+
});
3821+
expect(Scheduler).toHaveYielded([]);
3822+
expect(root).toMatchRenderedOutput(
3823+
<>
3824+
<span prop="Pending..." />
3825+
<span prop="" />
3826+
</>,
3827+
);
3828+
3829+
// Update to "b". That will suspend, too.
3830+
await ReactNoop.act(async () => {
3831+
setTextWithTransition('b');
3832+
expect(Scheduler).toFlushAndYield([
3833+
// Neither is resolved yet.
3834+
'Pending...',
3835+
'Suspend! [a]',
3836+
'Loading...',
3837+
'Suspend! [b]',
3838+
'Loading...',
3839+
]);
3840+
});
3841+
expect(Scheduler).toHaveYielded([]);
3842+
expect(root).toMatchRenderedOutput(
3843+
<>
3844+
<span prop="Pending..." />
3845+
<span prop="" />
3846+
</>,
3847+
);
3848+
3849+
// Resolve "a". But "b" is still pending.
3850+
await ReactNoop.act(async () => {
3851+
await resolveText('a');
3852+
});
3853+
expect(Scheduler).toHaveYielded([
3854+
'Promise resolved [a]',
3855+
'Pending...',
3856+
'a',
3857+
'Suspend! [b]',
3858+
'Loading...',
3859+
]);
3860+
expect(root).toMatchRenderedOutput(
3861+
<>
3862+
<span prop="Pending..." />
3863+
<span prop="a" />
3864+
</>,
3865+
);
3866+
3867+
// Resolve "b". This should remove the pending state.
3868+
await ReactNoop.act(async () => {
3869+
await resolveText('b');
3870+
});
3871+
expect(Scheduler).toHaveYielded(['Promise resolved [b]', 'b']);
3872+
// The bug was that the pending state got stuck forever.
3873+
expect(root).toMatchRenderedOutput(<span prop="b" />);
3874+
},
3875+
);
37613876
});

0 commit comments

Comments
 (0)