As the title suggest, I've got a NextJS component which renders NextUI Dropdowns using a object of data. The problem i'm facing right now is that when i have a dropdown already open and want to open anoter dropdown rendered by the component i must click it twice to get it open (one click for close the already open dropdown and one click for open the dropdown i want to).
The component is for a NextJS project using typescript, tailwind for styles and NextUI react library
This is the code of the component:
'use client'
import React, { useState } from 'react'
import {
Navbar,
NavbarContent,
NavbarItem,
Link,
DropdownMenu,
DropdownItem,
Dropdown,
DropdownTrigger,
Button
} from '@nextui-org/react'
export function NavbarFooter() {
const items = [
{
title: 'Dropdown 1',
dropdown: [
{ title: 'Subitem 1', path: '/' },
{ title: 'Subitem 2', path: '/' }
]
},
{
title: 'Dropdown 2',
dropdown: [
{ title: 'Subitem 1', path: '/' },
{ title: 'Subitem 2', path: '/' }
]
},
{ title: 'Item 3', path: '/' }
]
const [activeDropdown, setActiveDropdown] = useState<null | number>(null)
const handleDropdownClick = (index: number | null) => {
setActiveDropdown((prev) => (prev === index ? null : index))
}
return (
<Navbar
className="top-[4rem] w-full bg-[#BC9A22] px-0 md:h-[2.8rem]"
height="0.8rem"
maxWidth="2xl"
>
<NavbarContent justify="end" className="">
{items.map((item, index) =>
item.dropdown ? (
<NavbarItem key={`${item.title}-${index}`}>
<Dropdown
isOpen={activeDropdown === index}
onOpenChange={() => handleDropdownClick(index)}
>
<DropdownTrigger>
<Button>
{item.title}
{activeDropdown === index ? ' ▲' : ' ▼'}
</Button>
</DropdownTrigger>
<DropdownMenu>
{item.dropdown.map((subItem, subIndex) => (
<DropdownItem key={subIndex}>
<Link href={subItem.path}>{subItem.title}</Link>
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
</NavbarItem>
) : (
<NavbarItem key={`${item.title}-${index}`}>
<Link href={item.path}>{item.title}</Link>
</NavbarItem>
)
)}
</NavbarContent>
</Navbar>
)
}
It's done.
Using @Batman code's answer and adding shouldCloseOnInteractOutside={() => false}
in the Dropdown component it behaves as expected.
Explanation why:
By adding shouldCloseOnInteractOutside={() => false}
in the Dropdown fix the problem i was facing of having to click twice to open a dropdown when another one is already open. It fix the problem but add another one: It cancels the possibility to close the dropdown by clicking out of it.
To fix the new problem, the code that @Batman provided is very helpful (only the useEffect). With the function change is not posible to close the dropdown by clicking it again.
This is the modified and functional component:
'use client'
import React, { useEffect, useState } from 'react'
import {
Navbar,
NavbarContent,
NavbarItem,
Link,
DropdownMenu,
DropdownItem,
Dropdown,
DropdownTrigger,
Button
} from '@nextui-org/react'
export function NavbarFooter() {
const items = [
{
title: 'Dropdown 1',
dropdown: [
{ title: 'Subitem 1', path: '/' },
{ title: 'Subitem 2', path: '/' }
]
},
{
title: 'Dropdown 2',
dropdown: [
{ title: 'Subitem 1', path: '/' },
{ title: 'Subitem 2', path: '/' }
]
},
{ title: 'Item 3', path: '/' }
]
const [activeDropdown, setActiveDropdown] = useState<null | number>(null)
const handleDropdownClick = (index: number | null) => {
setActiveDropdown((prev) => (prev === index ? null : index))
}
useEffect(() => {
const closeDropdown = (event: MouseEvent) => {
// Cast the event target to an HTMLElement instance
const target = event.target as HTMLElement
// Check if the clicked element is part of a dropdown. If not, close the open dropdown.
const isDropdown =
target.closest('[role="listbox"]') ||
target.closest('[data-nextui-dropdown-trigger]')
if (!isDropdown) {
setActiveDropdown(null)
}
}
// Attach the event listener to the window
window.addEventListener('mousedown', closeDropdown)
// Clean up the event listener when the component is unmounted
return () => window.removeEventListener('mousedown', closeDropdown)
}, [])
return (
<Navbar
className="top-[4rem] w-full bg-[#BC9A22] px-0 md:h-[2.8rem]"
height="0.8rem"
maxWidth="2xl"
>
<NavbarContent justify="end" className="">
{items.map((item, index) =>
item.dropdown ? (
<NavbarItem key={`${item.title}-${index}`}>
<Dropdown
isOpen={activeDropdown === index}
onOpenChange={() => handleDropdownClick(index)}
shouldCloseOnInteractOutside={() => false}
>
<DropdownTrigger>
<Button>
{item.title}
{activeDropdown === index ? ' ▲' : ' ▼'}
</Button>
</DropdownTrigger>
<DropdownMenu>
{item.dropdown.map((subItem, subIndex) => (
<DropdownItem key={subIndex}>
<Link href={subItem.path}>{subItem.title}</Link>
</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
</NavbarItem>
) : (
<NavbarItem key={`${item.title}-${index}`}>
<Link href={item.path}>{item.title}</Link>
</NavbarItem>
)
)}
</NavbarContent>
</Navbar>
)
}