4

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;

ewcoder
  • 242
  • 3
  • 14

2 Answers2

2

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;

ewcoder
  • 242
  • 3
  • 14
0

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>
Vektor
  • 697
  • 5
  • 14