1

I have made a dropdown on React Js. I want the dropdown to close when I click outside using onBlur function. The problem here is that it also closes when a dropdown option is clicked, and doesn't execute the function attached to the option. I want to add a condition to the onBlur function which will check if area clicked was inside or outside the dropdown and act accordingly, but I am not sure how to check that.

I tried document.activeElement, but that is selecting the entire body element.

Here is my code Sandbox link: https://codesandbox.io/s/intelligent-tdd-zq4qcr?file=/src/App.js:938-941

This is the function I need help with:

const close = (event) => {
  // Help me with this function please
  // I need to wrap this function in a condition that checks
  // if the click was outside the dropdown
  let dropdown = event.target.nextSibling;

  // If I comment the lines below,
  // it updates the number, but doesn't close the dropdown
  if (dropdown?.classList.contains("display")) {
    dropdown.classList.remove("display");
    setOption("");
  }
};

I am adding a class with property display: unset when the dropdown button is clicked. By default, the display is set to none.

EDIT: The number of these dropdown in my original project are variable. They could be two or they could be 10, depending on the user. So I can't maintain it in the state and need to use JavaScript to track which dropdown is clicked.

2 Answers 2

2

You could store the open state and conditionally add the display class. To detect click outside of div, you can pass a ref to the wrapper div and use a custom hook.

Note that you should avoid manipulating the dom directly in general, if you feel like you have to do it, it most likely mean that you should revisit your approach.


function useOnClickOutside(ref, cb) {
    useEffect(() => {
        function handleClickOutside(event) {
            if (ref.current && !ref.current.contains(event.target)) {
                cb();
            }
        }

        // Bind the event listener
        document.addEventListener("mousedown", handleClickOutside);
        return () => {
            // Unbind the event listener on clean up
            document.removeEventListener("mousedown", handleClickOutside);
        };
    }, [ref, cb]);
}

function Dropdown({onSelect, values}) {
    const [isOpen, setIsOpen] = useState(false);
    const wrapperRef = useRef(null);

    const close = () => {
        setIsOpen(false);
    };

    useOnClickOutside(wrapperRef, close);

    const toggle = () => {
        setIsOpen(prev => !prev)
    };


    return (
        <div className={`dropdown`} tabIndex={0}>
            <button onClick={toggle}>•••</button>
            <div ref={wrapperRef} style={{zIndex: 999}} className={`list ${isOpen ? 'display' : ''}`}>
                <ul>
                    {values.map(v => <li
                        key={v}
                        onClick={() => {
                            onSelect(v)
                            setIsOpen(false)
                        }}>{"Option " + v}</li>)}

                </ul>
            </div>
        </div>
    )

}

export default function App() {
    const [option, setOption] = useState(null);


    return (
        <div className="App">
            <h2>Option: {option}</h2>
            <p>
                Dropdown should close when I click outside it
                <br/>
                Option Number should update
            </p>
            <Dropdown onSelect={setOption} values={[1, 2, 3]}/>
            <Dropdown onSelect={setOption} values={[4, 5, 6]}/>
            <Dropdown onSelect={setOption} values={[6, 7, 8]}/>
        </div>
    );
}


Sign up to request clarification or add additional context in comments.

3 Comments

The number of these dropdown in my project are variable. They could be two or they could be 10, depending on the user. So I can't maintain it in the state and need to use dom to track which dropdown is clicked.
You don't have to, you can just add an intermediary component that stores the state
I edited my answer to add multiple dropdowns
1

You had a great start, but there were some things missing. Instead of having to add/remove the class by yourself, you can create a state for that and let React deal with it for you.

Also if you add an onFocus event on your div, you can manage when the dropdown will be open.

export default function App() {
    const [option, setOption] = useState("");
    const [isOpen, setIsOpen] = useState(false);

    const close = () => {
        setIsOpen(false);
    }
    const open = () => {
        setIsOpen(true);
    }
    const selectValue = (event) => {
        const value = event.target.value;
        setOption(value);
        close();
    }

    return (
        <div className="App">
        <h2>Option: {option}</h2>
        <p>
            Dropdown should close when I click outside it
            <br />
            Option Number should update
        </p>

        <div className="dropdown" tabIndex={0} onBlur={close} onFocus={open}>
            <button onClick={open}>•••</button>

            <div className={`list ${isOpen ? "display" : ""} `}>
            <ul>
                <li value={1} onClick={selectValue}>Option 1</li>
                <li value={2} onClick={selectValue}>Option 2</li>
            </ul>
            </div>
        </div>
        </div>
    );
}

4 Comments

Thank you. But the number of these dropdown in my original project are variable. They could be two or they could be 10, depending on the user. So I can't maintain it in the state and need to use javascript to track which dropdown is clicked.
Then you should probably create a Dropdown component that would be responsible for the whole logic itself.
Ok. I'll try and see if that works and let you know. However, I won't accept this answer because I would still like to know if there is a way to do it the way I did. Like, if there is a way to check if user clicked outside the dropdown or not. It might not work here but it might be useful somewhere else. But thank you so much for helping! If it works, I'll upvote your comment.
This works! Thanks! I want to know one thing though, from this solution, the dropdown doesn't close if I click the button again, it only closes from the outside click. I tried using the toggle function on "onClick", but that causes it to open and close instantly on first time click because the dropdown has already been opened from "onFocus". But I can't remove the "onFocus" function because then the dropdown closes when I click on any option.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.