Home > Net >  React JS close dropdown using onBlur function
React JS close dropdown using onBlur function

Time:09-13

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.

CodePudding user response:

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>
    );
}

CodePudding user response:

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>
    );
}


  • Related