5

How do i change the internal state of the Popover with a function outside the component: Like the openPopover function i created below.

const openPopover = () => {
    setPopoverOpen(true) // Example..
}

<Popover>
  {({open}: {open: boolean}) => {
    // How do i change the open state here from outside the Popover.

    return (
      <>
        <Popover.Button>
          Test
        </Popover.Button>

        {open && (
          <Popover.Panel static>
            Test
          </Popover.Panel>
        )}   
      </>
    )
  }}
</Popover>
haveman
  • 311
  • 1
  • 3
  • 13
  • Is there a particular use case you have in mind. Since Popover officially doesn't support using an external state to manage its internal state. Although it does expose the internal state and a `close()` function for you to cover most use cases. – M. G. Dec 16 '22 at 04:44
  • Yeah its for opening the cart popover after clicking the "Add to Cart" button, just don't wanna use refs, as that is bad practice. – haveman Dec 16 '22 at 08:13
  • For carts on e-commerce we generally use `document. getElementById("#cart_button").click()`. Even Shopify default themes do the same. So it's common to do it with VanillaJS I believe. – M. G. Dec 17 '22 at 09:05

2 Answers2

1

Popover manages open state internally and doesn't expose a way to change this.

To keep track of the open state however, you could use a useEffect inside the render prop.

const [isPopoverOpen, setIsPopoverOpen] = useState(false);

<Popover>
  {({open}: {open: boolean}) => {
    useEffect(() => {
      setIsPopoverOpen(open)
    }, [open]);

    return (
      <>
        <Popover.Button>
          Test
        </Popover.Button>,
   
        {open && (
          <Popover.Panel static>
            Test
          </Popover.Panel>
        )}   
      </>
    )
  }}
</Popover>

The toggling behavior is being handled by <Popover.Button> component. Alternatively, Popover and Popover.Panel exposes a close() method to close the popover. You could always use Portals to make the component available in parent for handling either toggling or executing the close() method.

import { createPortal } from 'react-dom';

<Popover>
  {({ open, close }) => {
    return (
      <>
       {createPortal(
        <Popover.Button>
          Toggle popover
        </Popover.Button>,
        // html node where we will position this button
       )}

        {open && (
          <Popover.Panel static>
            Test
          </Popover.Panel>
        )}

       {createPortal(
         <button onClick={close()}>
            Close Popover
         </button>,
         // html node where we will position this close button
        )}
      </>
    )
  }}
</Popover>
Badal Saibo
  • 2,499
  • 11
  • 23
  • Im well aware u can use refs, i was looking for at more best-practice approach that doesn't clutter my application with refs for my popovers. And portals seem to duplicate the button in another location, which is not what im looking for. Its for a add to cart button that should open the cart. – haveman Dec 16 '22 at 08:16
  • Portal doesn't duplicate. It makes renders outside the DOM hierarchy possible. I had a similar use case for cart on a site, and I used popper js for that. I hope that could help. – Badal Saibo Dec 16 '22 at 08:38
  • Ur portal example works, just wondering if u could make it open the popover, instead of close? – haveman Dec 17 '22 at 18:57
  • That's the popover's API limitation. There was another answer here using the static method . I think the author removed it but it was also a good way. With static you manage the state, and based on it will it get rendered or not. [More here](https://headlessui.com/react/popover#showing-hiding-the-popover) – Badal Saibo Dec 19 '22 at 08:40
0

Here's a workaround, remove the open state condition and manipulate the static prop

<Popover>
  {({open}: {open: boolean}) => {
    useEffect(() => {
      setIsPopoverOpen(open)
    }, [open]);

    return (
      <>
        <Popover.Button>
          Test
        </Popover.Button>
   
          <Popover.Panel static> // manipulate this static prop
            Test
          </Popover.Panel>  
      </>
    )
  }}
</Popover>