[RAC] ComboBox popover with `triggerRef` is not positioned correctly on SSR
Provide a general summary of the issue here
ComboBox popover with triggerRef that points to a different element than the input, is positioned on top left of the page on initial page load in an SSR app.
๐ค Expected Behavior?
Popover to be positioned correctly on SSR too.
๐ฏ Current Behavior
Popover is positioned on top left of the page on SSR.
๐ Possible Solution
No response
๐ฆ Context
For our project I need to add an optional icon and an arbitrary slot before the Input. To achieve this I use the Group component to wrap the elements, and add a triggerRef to Popover, that references the Group component to position the popover menu correctly.
This works fine on Storybook but when we use the component on Remix or Next.js, on initial load of the page, the popover is aligned to the top left of the page. When we navigate to another page and come back, it works.
๐ฅ๏ธ Steps to Reproduce
https://stackblitz.com/edit/remix-run-remix-feff8g?file=app%2Froutes%2Fabout.tsx,app%2Froutes%2F_index.tsx
- Load the page
- Open ComboBox menu, and the menu is positioned incorrectly
- Go to "About" page
- Go to "Home" page
- Open ComboBox and the menu is positioned correctly.
Version
React Aria Components v1.2.1
What browsers are you seeing the problem on?
Chrome
If other, please specify.
No response
What operating system are you using?
MacOS
๐งข Your Company/Team
No response
๐ท Tracking Issue
No response
Definitely strange, I don't see anything particularly different between the Group and the Input/Button elements that would cause this difference in behavior, will need to dig into what in useOverlayPosition is causing this positioning.
For now I'm using this workaround.
const groupRef = React.useRef<HTMLDivElement>(null);
const triggerRef = React.useRef<HTMLDivElement | null>(null);
React.useLayoutEffect(() => {
if (groupRef.current) {
triggerRef.current = groupRef.current;
}
}, []);
<ComboBox>
<Group ref={groupRef}>
...
</Group>
<Popover triggerRef={triggerRef}>
...
</Popover>
</ComboBox>
Seems to have something to do with what runs on the server vs what runs on the client. I don't know enough about Remix, but does it work if you turn it into a client only component? (don't know if that exists in Remix)
From what I can tell though, this isn't something related to RAC
@snowystinger Remix doesn't have server components. I tested also with Next.js that has server components and added 'use client' directive. It wouldn't work without it anyways, and the same issue is there too. Here's the link.
https://stackblitz.com/edit/stackblitz-starters-xjutyc?file=app%2FComboBox.tsx
As @LFDanLu mentioned, the strange thing is that it works fine when you set the trigger element as the Button for example. That's why it feels like a RAC issue.
It's odd because Group is even more simple than Button, so I can't imagine why the ref is not defined once you get to that first effect.
I asked about the server vs client because I saw this https://remix.run/docs/en/main/discussion/server-vs-client#splitting-up-client-and-server-code
I don't know how their magic splitting works.
It's good that it happens in Next.js though, I think we can go back to suspecting RAC.
I thought maybe the order in which renders/refs/effects ran in children/siblings might be a culprit, but then I would expect this to affect non-SSR as well. I also would expect that adding a wrapper around Group would solve it, and that didn't do anything. So I think we can rule that out as well.
The way I solved this for us is to use the useIsSSR hook and attach the ref only on the client
Here's a modified example from above https://stackblitz.com/edit/stackblitz-starters-mtega3?file=app%2FComboBox.tsx