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

Transition issue with React Suspense #2400

Closed
jorisre opened this issue Mar 29, 2023 · 6 comments · Fixed by #2633 or #2663
Closed

Transition issue with React Suspense #2400

jorisre opened this issue Mar 29, 2023 · 6 comments · Fixed by #2633 or #2663
Assignees

Comments

@jorisre
Copy link

jorisre commented Mar 29, 2023

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

"@headlessui/react": "^1.7.13"

What browser are you using?

Chrome

Reproduction URL

Github repro: https://github.com/jorisre/headlessui-suspense-issue

Describe your issue

When using two <Transition /> components through <Portal /> (or other) in combination of React <Suspense /> we get a React rendering issue (see screen below).

I tried to fix it directly inside the @headlessui/react without success. It seems the issue is related to useServerHandoffComplete and env.ts. If someone have a hint that helps me to fix it ✌🏻

Screenshot 2023-03-29 at 12 51 15

ezgif com-optimize

Steps to reproduce:

  1. Clone the repo https://github.com/jorisre/headlessui-suspense-issue
  2. Run npm install
  3. And npm run dev
  4. Then go to http://localhost:3000 and open the browser console to see the error
@jorisre
Copy link
Author

jorisre commented Apr 11, 2023

@RobinMalfait any insights?

@thecrypticace thecrypticace self-assigned this Apr 28, 2023
@thecrypticace
Copy link
Contributor

Leaving some notes for myself for later research:

  • <Suspense> delays SSR hydration until the component is ready / promise is resolved. React calls this selective hydration.
  • Selective Hydration means that assumptions about hydration happening immediately upon page load are no longer correct
  • We have a global hook called useServerHandoffComplete which basically tells us if hydration has happened or not. We use this because we need to know how to render things to match the server — there may be times when we have to do one thing because browser APIs aren't available on the server.
  • Since useServerHandoffComplete() is a "global" indicator it doesn't work with <Suspense>. This is because it assumes that hydration is a one-and-done thing.
  • The delay in hydration means that global state is no longer an appropriate solution and likely we'll need to use local state on a per-component basis instead.
  • This will cause needless double-renders but it's the only way to ensure that the component is rendered correctly on the server and the client.
  • If React provided a way to know when a component was actively hydrating, we could use that to avoid the double-render.

@thecrypticace
Copy link
Contributor

Hey! We made some changes in #2633 which basically uses a localized version of useServerHandoffComplete in the few places that are needed and removes it elsewhere where it is/was unneeded.

This will be available via our insiders build in a few minutes — which you can use to try out the changes/fixes:

npm install @headlessui/react@insiders

@thecrypticace
Copy link
Contributor

Ah looks like there's some regression with transitions inside dialogs that we have to look at. I'm gonna re-open this until we have a chance to figure that out.

@thecrypticace
Copy link
Contributor

Okay, so I figured out a more targeted "only when using React v18" fix for this in #2663. Should be fixed for real now. 😄

This will be available via our insiders build in a few minutes — which you can use to try out the changes/fixes:

npm install @headlessui/react@insiders

@nfiacco
Copy link

nfiacco commented Oct 31, 2023

Think this issue still happens in some situations— have one case where the transition seems to hydrate correctly within a suspense and one where it does not, in the same app. Trying to figure out why

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