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] When multiple components call use inside a Suspense, it crosses the boundary of Suspense and stops rendering of other Suspense as well #29905

Closed
yatsuna827 opened this issue Jun 15, 2024 · 4 comments

Comments

@yatsuna827
Copy link

Summary

import { Suspense, use, useState } from "react";

const f = async (wait) => {
  await new Promise((resolve) => setTimeout(resolve, wait));
  return new Date();
};

const App = () => {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Expected />
      <Unexpected />
    </>
  );
};

const Expected = () => {
  const [open, setOpen] = useState(false);

  return (
    <>
      <div>👍</div>
      <button onClick={() => setOpen(!open)}>{open ? "close" : "open"}</button>
      {open && <List wait={[1000, 2000, 3000, 4000, 5000]} />}
    </>
  );
};

const Unexpected = () => {
  const [open, setOpen] = useState(false);

  return (
    <>
      <div>🤔</div>
      <button onClick={() => setOpen(!open)}>{open ? "close" : "open"}</button>
      {open && <List wait={[1000, 2000, 3000, 3000, 5000]} />}
    </>
  );
};

const List = ({ wait }) => {
  const [t1] = useState(f(wait[0]));
  const [t2] = useState(f(wait[1]));
  const [t3] = useState(f(wait[2]));
  const [t4] = useState(f(wait[3]));
  const [t5] = useState(f(wait[4]));

  return (
    <>
      <Suspense fallback={<div>Loading No.1 ...</div>}>
        <Item time={t1} />
      </Suspense>
      <Suspense fallback={<div>Loading No.2 ...</div>}>
        <Item time={t2} />
      </Suspense>
      <Suspense fallback={<div>Loading No.3 ...</div>}>
        <Item time={t3} />
      </Suspense>
      <Suspense fallback={<div>Loading No.4 & No.5 ...</div>}>
        <Item time={t4} />
        <Item time={t5} />
      </Suspense>
    </>
  );
};

const Item = ({ time }) => {
  const t = use(time);

  console.log("render item");

  return <div>{t.toISOString()}</div>;
};

export default App;

https://codesandbox.io/p/sandbox/use-and-suspense-hlcjk6

The two components <Expected>👍 and <Unexpected>🤔 are expected to behave in the same way. That is, No.1 to No.3 should be displayed one second apart, and No.4 and No.5 should be displayed two seconds after No.3 is displayed.

There is a difference in the waiting time for No.4 between <Expected> and <Unexpected>, being 4 seconds or 3 seconds, but since they are wrapped together in <Suspense> with No.5, they should be throttled and displayed after 5 seconds.

However, in <Unexpected>, not only that, but No.3, which should belong to a different <Suspense> scope, is also displayed after 5 seconds. This seems to break the behavior of <Suspense>.


Please note that this is a different issue from the one being discussed in #26380. I am issuing a Promise outside of Suspense and passing it as props.

@yatsuna827
Copy link
Author

Furthermore, I have investigated a few more examples. When using a class like Recoil's Loadable instead of the use API, this issue does not occur.

And by wrapping Loadable and use in the same Suspense, the strange behavior of use becomes clear. When multiple Promises are thrown within a single Suspense, it seems that all Suspense boundaries are halted until the Promise thrown by use is resolved. This does not seem like the intended behavior to me.

https://codesandbox.io/p/sandbox/use-and-suspense-forked-skjk48

@eps1lon
Copy link
Collaborator

eps1lon commented Jun 25, 2024

2 Suspense boundaries is sufficient to reproduce this behavior: https://codesandbox.io/p/sandbox/crazy-browser-88t26y?file=%2Fsrc%2Findex.js%3A7%2C1

Flagged this internally to check if it's intended or not.

@yatsuna827
Copy link
Author

This problem appears to be fixed in rc.1

#29898 (comment)

@eps1lon
Copy link
Collaborator

eps1lon commented Nov 19, 2024

Fixed somewhere in 380f5d6...b01722d. Current thinking is that sibling prewarming fixed this which we're tracking in #29898

@eps1lon eps1lon closed this as completed Nov 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants