React vs JS event handlers for clicking outside the component, which one is best practice?

29 Views Asked by At

I am creating a menu component in React where when the menu opens, and user clicks anywhere outside the menu it'll close the menu component. There are two approaches I saw, one is to use JS event listener like this

import React, { useState, useEffect } from 'react';

const Dropdown = () => {
  const [isOpen, setIsOpen] = useState(false);
  
  const toggleDropdown = () => setIsOpen(!isOpen);

  useEffect(() => {
    const handleClickOutside = (event) => {
      if (isOpen && !event.target.closest('.dropdown')) {
        setIsOpen(false);
      }
    };

    document.addEventListener('mousedown', handleClickOutside);
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [isOpen]);

  return (
    <div className="dropdown" onClick={toggleDropdown}>
      {isOpen ? (
        <div className="dropdown-menu">
          {/* Dropdown Content */}
        </div>
      ) : null}
    </div>
  );
};

Another approach would be in with React event handler like this:

import React, { useState } from 'react';

const Dropdown = () => {
  const [isOpen, setIsOpen] = useState(false);

  // Toggle Dropdown open/close
  const toggleDropdown = () => setIsOpen(!isOpen);

  // Close Dropdown if open, when clicking outside
  const handleClickOutside = (e) => {
    if (isOpen && !e.currentTarget.contains(e.target)) {
      setIsOpen(false);
    }
  };

  return (
    <div onClick={handleClickOutside}>
      <button onClick={(e) => {
        e.stopPropagation(); // Prevents click from "bubbling" up to the div
        toggleDropdown();
      }}>
        Toggle Dropdown
      </button>
      {isOpen && (
        <div className="dropdown-menu">
          {/* Dropdown content */}
        </div>
      )}
    </div>
  );
};

so which one is more of a best practice ? on one hand you have to assign global event listeners, on one hand you are dealing with event bubbling in react.

1

There are 1 best solutions below

0
Tim Jensen On

Well, the idiomatic way is for anything within React to be React. So yea, stick to synthetic events. Bear in mind that this is only applicable in "reactive" behavior.

Set that aside for a moment, and instead focus on Event API. So, once you click a button, that button gains focus. Once you click something else, said button loses focus and fires the blur event. Now, instead of listening to a generic click event we can ulitlize this, and thus:

import React, {useState} from 'react';

export function Foo(props) {
  const [isOpen, setIsOpen] = useState(false)

  const handleBlur = (e) => {
    setIsOpen(false)
  }

  const handlePointer = (e) => {
    setIsOpen(!isOpen)
  }

  return (
    <div>
      <button onPointerDown={handlePointer} onBlur={handleBlur}>Click me</button>
      {isOpen ? <div>I'm showing!</div> : null}
    </div>
  );
}

Don't mind me registering onPointerDown instead of onClick. It's a force of habit. Here's the MDN