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

Passing ref to PopoverButton the ref get nullified on rerenders #3413

Closed
ori-shalom opened this issue Aug 3, 2024 · 1 comment
Closed

Passing ref to PopoverButton the ref get nullified on rerenders #3413

ori-shalom opened this issue Aug 3, 2024 · 1 comment

Comments

@ori-shalom
Copy link

ori-shalom commented Aug 3, 2024

What package within Headless UI are you using?
@headlessui/react

What version of that package are you using?
v2.1.2

What browser are you using?
Chrome

Reproduction URL
https://codesandbox.io/p/devbox/affectionate-microservice-87nfcg

Describe your issue
When trying to pass a ref to PopoverButton the ref get nullified when the component rerender.

For example given the following code:

import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
import { createRef, useEffect, useState } from 'react'

export function App() {
  const btnRef = createRef()
  const [counter, setCounter] = useState(0)
  useEffect(() => console.log(btnRef.current), [btnRef])

  return (
    <div className="grid h-screen w-screen place-content-center">
      <button onClick={() => setCounter((c) => c + 1)}>{counter}</button>
      <Popover>
        <PopoverButton ref={btnRef}>Popover Btn</PopoverButton>
        <PopoverPanel anchor={{ gap: 1 }}>Panel</PopoverPanel>
      </Popover>
    </div>
  )
}

Opening the browser console we can see that initially the ref is pointing to the button correctly but once we click on the counter button and the component rerender the console printing shows that the ref is not pointing at null.

Motivation
What I'm trying to do is being able to open the popover manually based on other custom events.
With the lack of a better way to do it, I thought I would use a ref to the button and call ref.current.click() to toggle the popover state.

A better way for me to accomplish that would be to have an handle ref or something on the Popover itself that exposes an open() function but with the lack of such way to open it programmatically I tried the ref on the button and got blocked by this bug.

Workaround
For now I found a workaround which is creating another button inside the PopoverButton and setting the ref on it instead.
Something like this:

<PopoverButton as={Fragment}><button ref={btnRef}>Popover Btn</button></PopoverButton>
@RobinMalfait
Copy link
Member

Hey!

The problem is this line:

  const btnRef = createRef()

The createRef() is not stable across re-renders. If you swap this with a useRef then it should work as expected:

  import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
- import { createRef, useEffect, useState } from 'react'
+ import { useRef, useEffect, useState } from 'react'

  export function App() {
-   const btnRef = createRef()
+   const btnRef = useRef()
    const [counter, setCounter] = useState(0)
    useEffect(() => console.log(btnRef.current), [btnRef])

    return (
      <div className="grid h-screen w-screen place-content-center">
        <button onClick={() => setCounter((c) => c + 1)}>{counter}</button>
        <Popover>
          <PopoverButton ref={btnRef}>Popover Btn</PopoverButton>
          <PopoverPanel anchor={{ gap: 1 }}>Panel</PopoverPanel>
        </Popover>
      </div>
    )
  }

Hope this helps!

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

No branches or pull requests

2 participants