react-spectrum icon indicating copy to clipboard operation
react-spectrum copied to clipboard

[RAC] ComboBox popover with `triggerRef` is not positioned correctly on SSR

Open ozguruysal opened this issue 1 year ago โ€ข 6 comments

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

  1. Load the page
  2. Open ComboBox menu, and the menu is positioned incorrectly
  3. Go to "About" page
  4. Go to "Home" page
  5. 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

ozguruysal avatar Jun 11 '24 13:06 ozguruysal

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.

LFDanLu avatar Jun 12 '24 17:06 LFDanLu

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>

ozguruysal avatar Jun 12 '24 20:06 ozguruysal

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 avatar Jun 20 '24 01:06 snowystinger

@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.

ozguruysal avatar Jun 21 '24 22:06 ozguruysal

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.

snowystinger avatar Jun 28 '24 01:06 snowystinger

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

shiba-codes avatar Oct 25 '24 13:10 shiba-codes