Search code examples
javascriptreactjstailwind-uiheadless-ui

Changing Popover internal state - Headless UI


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>

Solution

  • 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>