Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[React 19] useTransition()'s pending state does not go back to false (revision 94eed63c49-20240425) #28923

Open
alexeyraspopov opened this issue Apr 26, 2024 · 3 comments
Labels

Comments

@alexeyraspopov
Copy link
Contributor

Summary

I am excited to start using React v19 as it has so many features and QoL improvements I've been waiting for!

There is a bug (new bug comparing to v18.2.0) that I found while reproducing #26814. When using useTransition() with use(), pending flag of transition correctly becomes true in the beginning, but doesn't go back to false after transition is complete, which means any pending state artifacts in the UI remain visible.

Repository for reproducing: https://github.com/alexeyraspopov/react-beta-bugs.

Basic code (since the repo contains more than just this bug):

function SimpleAsyncFlow() {
  // this state holds instance of a promise that resolves after a second
  // the same `delayed()` function is used to trigger state update inside SimpleControlledDisplay
  let [value, setValue] = useState(() => delayed(Math.random()));
  return (
    <Suspense fallback={<p>Loading</p>}>
      <SimpleControlledDisplay promise={value} onChange={setValue} />
    </Suspense>
  );
}

function SimpleControlledDisplay({
  promise,
  onChange,
}: {
  promise: Promise<number>;
  onChange: (value: Promise<number>) => void;
}) {
  let value = use(promise);
  let [pending, startTransition] = useTransition();
  let click = () => {
    // this will trigger state update in the parent with the new 1 second promise 
    // that suppose to suspend this component, so transition should prevent it
    startTransition(() => {
      onChange(delayed(Math.random()));
    });
  };
  return (
    <div>
      <button onClick={click}>Refresh</button>
      {/* as UI "pending" state I update text style to become half transparent */}
      <p style={{ opacity: pending ? 0.5 : 1 }}>{value}</p>
    </div>
  );
}

function delayed<T>(value: T) {
  return new Promise<T>((resolve) => setTimeout(resolve, 1000, value));
}

Additionally here's the video reproduction, using the code from the repo I mentioned:

Screen.Recording.2024-04-26.at.08.27.42.mov
@eps1lon
Copy link
Collaborator

eps1lon commented Apr 26, 2024

Does this reproduce in dev and prod?

@alexeyraspopov
Copy link
Contributor Author

@eps1lon I made production build via npx vite build and the behavior remains the same.

@edunomatseye
Copy link

Maybe not a good idea to pass an async value directly or either through a function into state as this behaviour is synchronous. this could result to state inconsistency and also concurrency issues. looking at the delayed function passed into state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants