I have a parent component that holds multiple Dropdown child components. The state is managed within the parent component to show only one dropdown at a time. I currently have part of it working but am having some trouble wrapping my head around the logic to make it so that when you click outside of a dropdown if its open then it will close the dropdown. I have tried using useRef hook to detect click outside, but still having trouble wrapping my head around the logic to make things display correctly.
const MultipleDropdownPage = () => {
const [dropdown, setDropdown] = useState(null);
const handleDropdown = id => {
if (dropdown === id) {
setDropdown(null);
}
if (dropdown !== id) {
setDropdown(id);
}
};
return (
<div>
{dropdown ? dropdown : 'Nothing'}
<Dropdown handleDropdown={handleDropdown} dropdown={dropdown} id='1' />
<Dropdown handleDropdown={handleDropdown} dropdown={dropdown} id='2' />
</div>
);
};
import React from 'react';
const Dropdown = ({ handleDropdown, id, dropdown }) => {
return (
<div>
<button onClick={() => handleDropdown(id)}>Click me</button>
{id === dropdown && (
<div className='dropdown'>
<ul>
<li>Lorem, ipsum.</li>
<li>Dolore, eligendi.</li>
<li>Quam, itaque!</li>
</ul>
</div>
)}
</div>
);
};
export default Dropdown;
Needed to set a class on the button itself and then check if when the document is clicked it doesn't match that button class
import React, { useRef, useEffect } from 'react';
const Dropdown = ({ handleDropdown, id, dropdown }) => {
const ref = useRef();
useEffect(() => {
const handleClick = e => {
if (!e.target.classList.contains('dropdown-toggle')) {
handleDropdown(null);
}
};
document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, [handleDropdown]);
return (
<>
<button
onClick={() => handleDropdown(id)}
ref={ref}
className='dropdown-toggle'
>
Click me
</button>
{id === dropdown && (
<div className='dropdown'>
<ul>
<li>Lorem, ipsum.</li>
<li>Dolore, eligendi.</li>
<li>Quam, itaque!</li>
</ul>
</div>
)}
</>
);
};
export default Dropdown;
This solution takes advantage of the custom HTML data-* attribute.
const MultipleDropdownPage = (props) => {
const [dropdown, setDropdown] = React.useState(null);
const handleDropdown = id => {
if (dropdown === id || dropdown && id == undefined) {
setDropdown(null);
}
if (dropdown !== id) {
setDropdown(id);
}
};
React.useEffect(() => {
const handleClick = ({target}) => {
handleDropdown(target.dataset.id);
};
document.addEventListener("click", handleClick);
return () => document.removeEventListener("click", handleClick)
}, [handleDropdown]);
return (
<div className="dropdown-container">
{dropdown ? dropdown : 'Nothing'}
<Dropdown dropdown={dropdown} id='1' />
<Dropdown dropdown={dropdown} id='2' />
</div>
);
};
const Dropdown = ({ id, dropdown }) => {
return (
<div>
<button data-id={id}>Click me</button>
{id === dropdown && (
<div className='dropdown'>
<ul>
<li>Lorem, ipsum.</li>
<li>Dolore, eligendi.</li>
<li>Quam, itaque!</li>
</ul>
</div>
)}
</div>
);
};
ReactDOM.render(<MultipleDropdownPage />, document.getElementById("root"));
.dropdown-container {
padding: 5px;
border: 2px solid red;
width: fit-content;
margin: 0 auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>