Changing Popover internal state - Headless UI
Asked Answered
E

2

6

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>
Eleonoreleonora answered 13/12, 2022 at 6:9 Comment(3)
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.Becalmed
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.Eleonoreleonora
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.Becalmed
S
2

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>
Stifle answered 16/12, 2022 at 6:44 Comment(4)
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.Eleonoreleonora
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.Stifle
Ur portal example works, just wondering if u could make it open the popover, instead of close?Eleonoreleonora
That's the popover's API limitation. There was another answer here using the static method <Popover.Panel static></Popover.Panel>. 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 hereStifle
Y
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>
Youngstown answered 14/3, 2023 at 16:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.