Reputation: 880
I'm trying to implement Popover from https://headlessui.dev/react/popover, but I cannot quite figure out how I can get the menu to close when clicking the menu items.
I'm using NextJS and Link, and that is where I see the behavior. When I am only using < a > tags, then it reloads the page and the menu is closed on reload, but I would like to leverage Link from NextJS.
This is my header file simplified without class names:
import React, { Fragment, useState } from "react";
import { Popover, Transition } from "@headlessui/react";
import Link from "next/link";
export default function Header() {
const [navbar, setNavbar] = useState(false);
const changeNavbar = () => {
if (window.scrollY >= 80) {
setNavbar(true);
} else {
setNavbar(false);
}
};
React.useEffect(() => {
window.addEventListener("scroll", changeNavbar);
}, []);
return (
<Wrapper>
<div>
<Popover>
{({ open }) => (
<>
<div>
<div>
<div>
<span>Name</span>
<Link href="/">
<img
src="/images/logos/logo_blue.png"
alt=""
/>
</Link>
</div>
<div>
<Popover.Button>
<span>Open menu</span>
<MenuIcon aria-hidden="true" />
</Popover.Button>
</div>
<Popover.Group as="nav">
{main.map((item) => (
<Link href={item.href} key={item.name}>
<a key={item.name}>
{item.name}
</a>
</Link>
))}
<Popover>
{({ open }) => (
<>
<Popover.Button>
<span>More</span>
<ChevronDownIcon
aria-hidden="true"
/>
</Popover.Button>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel
static>
<div>
<div>
{resources.map((item) => (
<a
key={item.name}
href={item.href}
>
<item.icon
aria-hidden="true"
/>
<div>
<p>
{item.name}
</p>
<p>
{item.description}
</p>
</div>
</a>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
</Popover.Group>
<div>
<a
href="/login">
Sign in
</a>
<a
href="/login"
>
Sign up
</a>
</div>
</div>
</div>
<Transition
show={open}
as={Fragment}
enter="duration-200 ease-out"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="duration-100 ease-in"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Popover.Panel
focus
static
>
<div>
<div>
<div>
<Link href="/">
<img
src="/images/logos/logo_blue.png"
alt="Workflow"
/>
</Link>
<div className="-mr-2">
<Popover.Button>
<span >Close menu</span>
<XIcon aria-hidden="true" />
</Popover.Button>
</div>
</div>
<div>
<nav>
{main.map((item) => (
<a
key={item.name}
href={item.href}
>
<item.icon
aria-hidden="true"
/>
<span>
{item.name}
</span>
</a>
))}
</nav>
</div>
</div>
<div>
<div>
{resources.map((item) => (
<a
key={item.name}
href={item.href}
>
{item.name}
</a>
))}
</div>
<div>
<a
href="/login"
>
Sign up
</a>
<p>
Existing customer?{" "}
<a href="/login">
Sign in
</a>
</p>
</div>
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
</div>
</Wrapper>
);
}
Upvotes: 4
Views: 14651
Reputation: 23
According to the Headless UI documentation, "The Next.js Link
component does not forward unknown props to the underlying a
element, so it won't close the menu on click when used inside a Menu.Item
."
You can use the useRouter
hook on the Menu.Item
component to avoid using the Link
component.
import { Menu } from "@headlessui/react";
import { useRouter } from "next/router";
function MyDropdown() {
const router = useRouter();
return (
<Menu>
<Menu.Button>
<button>More</button>
</Menu.Button>
<Menu.Items>
<Menu.Item
as="div"
onClick={() => {
router.push({
pathname: href,
});
}}
>
<a>Account Settings</a>
</Menu.Item>
</Menu.Items>
</Menu>
);
}
Upvotes: 0
Reputation: 129
For anyone looking for an answer, Headless UI wrote a workaround for this problem:
https://headlessui.dev/react/menu#integrating-with-next-js
The solution is to use a custom Link wrapper:
const MyLink = forwardRef((props, ref) => {
let { href, children, ...rest }: any = props;
return (
<Link href={href}>
<a ref={ref as any} {...rest}>
{children}
</a>
</Link>
)
})
Replace the Menu with the above link:
{menuItems.map((each, ix) =>
<Menu.Item key={ix}>
{({ active }) => (
<MyLink href={each.link}>
{each.name}
</MyLink>
)}
</Menu.Item>
)}
Upvotes: 3
Reputation: 4076
Wrap your button/link with the internal close state variable and then trigger the onClick close()
<Popover.Panel static focus>
{({ close }) => (
<span onClick={() => close()}>This button will close the panel</span>
)}
</Popover.Panel>
Upvotes: 2
Reputation: 76
I am using TailwindCSS and HeadlessUI on a ReactJS project for my school and I was wondering the same thing.
My solution was to wrap the popover panel items in a Popover.Button and invoke a function that set the Popover's own 'open' to false and as a result the menu will close.
I'm taking only 1 example from your code:
{main.map((item) => (
<Popover.Button onClick={() => (open = false)}>
<Link href={item.href} key={item.name}>
<a key={item.name}>
{item.name}
</a>
</Link>
<Popover.Button>
I hope this helps solve your case as well as it did for me :)
Upvotes: 6