react: Bug: weird `useTransition` behaviour
A couple examples of unexpected and seemingly broken useTransition behaviour
1. useTransition doesn’t work, if the component is suspended before useTransition call:
Sandbox: https://codesandbox.io/s/gracious-lumiere-242bo?file=/src/First.js
Code:
export default function First() {
cache.read(); // <-- read from cache first
const [startTransition, isPending] = React.unstable_useTransition(config); // <-- call useTransition after
const [rev, setRev] = React.useState(0);
function reload() {
cache.expire();
startTransition(() => {
setRev(rev => rev + 1);
});
}
return (
<div>
{isPending ? "Pending" : "Not pending"}
<br />
{rev}
<br />
<button onClick={reload}>Reload</button>
</div>
);
}
The current behavior
Fallback is always shown
The expected behavior
Fallback is never shown
This issue is somewhat fixed if cache read and useTransition are just swapped, but then we get issue number 2:
2. useTransition prevents fallback, but isPending is either always false, or true for small period of time:
Sandbox: https://codesandbox.io/s/gracious-lumiere-242bo?file=/src/Second.js
Code:
export default function Second() {
const [startTransition, isPending] = React.unstable_useTransition(config); // <-- call useTransition first
cache.read(); // <-- read from cache after
const [rev, setRev] = React.useState(0);
function reload() {
cache.expire();
startTransition(() => {
setRev(rev => rev + 1);
});
}
return (
<div>
{isPending ? "Pending" : "Not pending"}
<br />
{rev}
<br />
<button onClick={reload}>Reload</button>
</div>
);
}
This works much better, but still has some issues
The current behavior
- fallback is still sometimes shown, mostly when you click the button more than once quickly
isPendingis either always false, or true for small period of time (you can see the flash)
The expected behavior
- fallback is never shown
isPendingis true while suspended
This is probably the same issue as https://github.com/facebook/react/issues/19046
React version: "0.0.0-experimental-4c8c98ab
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 27 (3 by maintainers)
@arackaf
We’ll definitely share the guidelines when the overall story is more fleshed out. I get that it’s annoying to wait for, but we also don’t want to create noise now precisely because, as you said, we’ve done that too early in the past with demos, and that caused some churn. In particular, we expect to make some progress in the caching area, so that many solutions would be able to rely on our cache (or at least, some shared infrastructure). In that case, instructing people to implement one manually today seems like creating more extra work and churn, and as you noted, it does get pretty mind-bendy doing everything yourself.
Ok, I think we can close it. It turned out be more of a discussion than a bug report, but it was truly enlightening.
Also I’ve noticed that if you do need to force a rerender for anything (like I did to trigger a refetch), your code is very likely incorrect
Yup!
Whoa - thanks a ton @gaearon. Super useful explanation.
The only thing I’d suggest is that being more resilient to this wouldn’t necessarily mean “supporting broken use cases” better, but rather might help expose them better. So rather than “why does isPending flicker, this must be a bug” it’d be more “why does the fallback show” which is slightly more indicative of broken user code.
OK, I can dig into this. I haven’t because usually I stop looking as soon as I see the rules being broken. (In this case, a mutation that makes it impossible to re-render the first version.)
Arrrggghhhh so you need to eject the cache in the “new” world, but keep it around in the “old” world. Which is why @gaearon said to keep your cache in state. I understand now.
In particular, in Relay, the cache is mutable. I’ll ask someone from Relay to briefly describe how they make it work.