primitives: [Portal] Hydration failure in SSR when initially rendered
This manifests itself in Dialog and AlertDialog when using Portal with DefaultOpen in SSR env.
https://codesandbox.io/s/friendly-morning-3ny0r5?file=/pages/index.tsx
About this issue
- Original URL
- State: open
- Created 2 years ago
- Reactions: 52
- Comments: 36
The same issue here when working with next.js, any plan to fix this? Now my workaround is set the
opentofalsedefault, then set ittrueinuseEffectwhen page has mounted.Bruh why has this not been fixed š¦
This fixed it for meā¦
<DialogPrimitive.Portal container={document.body}>Bump +1ļøā£
TL;DR: Leaky abstraction.
I think it is important to realize right now that we are pursuing what we might not want in the end. Hydration, in my opinion, is the non-essential ingredient for web development. In the traditional way of doing web development, there are only HTML, CSS and JS. Do we need hydration to do dialogs in HTML, CSS and JS? No, but we have to right now, because hydration is a side effect of wanting to use a UI ālibraryā to manage the DOM and its interactivity. Thereās nothing wrong with that if you need a UI ālibraryā (not just for rendering the elements, but also organizing interactions and states), but UI ālibraryā and a server, hence hydration, takes time to learn, which traditional websites and client-side apps doesnāt have to.
With that said, looking at the Radix UI code, I think it is too complex (saying this from the perspective of a developer taking Radix UIās idea and build a composable unstyled UI component internally). Maybe it has to be that way to accommodate all of the components there are, but if youāre using Radix UI and you hit a stone (just as I do when I have to write my own composable UI for a āScrollable Horizontal Listā and potentially wants to contribute to Radix UI), youāre damned. Either spend 8 hours becoming a maintainer of Radix UI just to write your component that extends Radix UIās primitives, or you have to start from scratch.
Stones, like Radix UIās hydration error. Do you understand exactly why even with
open={true}, the dialog still not open on the Server-side render pass? Thereās no way React do this by default - settingopen={true}will just do nothing and decides:Is it React Portalās fault? Is it Radix UIās
<Portal>'s fault? Is it the fact that the position of the dialog depends on the presence of the client-side because guessing the height of the userās viewport on the server is outright impossible? How do you go ahead and resolve the issue (and this GitHub issue altogether)?So maybe the next time you got problems with Radix UIās SSR problem, maybe, just maybe, realize that Radix UI is working around Reactās problems just to be an unstyled UI library. Maybe you just have to understand everything and solve it yourself. I hope that you have the same vision as me, about the future where web development starts with HTML, CSS and JS (i.e. unstyled UI library/components for the web), not React (unstyled components in React).
Weāre running into this as well. Building on top of @adomaitiscās solution, hereās a component that only replaces the Root component (in this case for the
Dialog, but could beAlertDialogjust as well):You can then use this custom
Rootinstead of the default one.FYI headlessuiās portal works out of the box in NextJS SSR, curious how they did it, but the code is too complicated for me to understand https://github.com/tailwindlabs/headlessui/blob/main/packages/%40headlessui-react/src/components/portal/portal.tsx
Simply using
asChildon the Trigger component worked for me. Note my Dialog is not controlled. See this answer for details.Hey, I had this issue just now and I fixed by making it a client component + useState + useEffect.
I am using next 13.4.3, with @radix-ui/react-alert-dialog 1.0.4
Here is a working version using the āopenā prop to AlertDialogās root.
@JacobGrady @adomaitisc @iamshubhamjangle look into your console, document doesnāt exist on your server. It might cause hydration issues down the line.
i think this issue affect all user that use third party animation like framer motion
workaround iam using, while waiting for this to be fixed
For me, this is a blocking issue, especially if I want Portal content to be crawlable for SEO purposes. Headless UI supports that.
I believe you should mention this limitation in your docs at least, so users donāt waste time trying to make it work
All the above workarounds, are just to bypass rehydration issues, but still Portal isnāt rendered on the server side
I just did this to fix the current issue.
This also creates some big problems when using astro & SSG with any radix-ui component.
Can we get a permanent fix for this that allows components to be hydrated correctly?
see issue I opened which got closed -> https://github.com/radix-ui/primitives/issues/2164
I ran into this issue and thereās not much you can do other than not use Portals, since they depend on the DOM. The easiest work around to get Dialogs working with SSR with
default=openis to just render you dialog high enough in the component tree, which effectively removes the need for Portals entirely.I abstracted this a bit in my component by allowing a
Portal?: React.ElementType;prop and having it default toPortal = DialogPrimitive.Portalso if I want to opt out of portals I can just passPortal={React.Fragment}. Hopefully this helps someone else!this is the easiest way
I got the same issue and fixed it by creating the following component
If you want it open from the start then donāt use Dialog.Portal, that simple. If you use ReactDOM.createPortal (Dialog.Portal) you have to have a reference element to attach the content to, and you donāt have a DOM on the server, so you canāt do that. Lets say you want to instantly show a dialog, you use Portal and have a form in there. Because server canāt render it server side, you will be rendering null as your first render and then send all of portal content serialized js to the client where it will be rendered, whereās the SEO? While if you just omit Portal and render the dialog with form content directlyā¦
Am I missing something why you might want Portal?
@intagaming I agree with most of what you say. But the reality is that we need hydration because otherwise pages are super slow (mostly because, letās face it react is slow).
Also, I understand that dialogs and such depend on the size of the screen and therefore cannot hydrate. However, components such as dropdowns, if not open by default should be able to hydrate without issue.
Just ran into this issue as well, using this fix
Toast viewport is broken as well and I canāt seem to find a workaround for it, no container prop and it doesnāt matter if thereās no toasts open, so no useEffect hack either.