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
  • isPending is either always false, or true for small period of time (you can see the flash)

The expected behavior

  • fallback is never shown
  • isPending is 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)

Most upvoted comments

@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.

Does this mean that cache should essentially be immutable, or there is a middleground?

In particular, in Relay, the cache is mutable. I’ll ask someone from Relay to briefly describe how they make it work.