Hone logo
Hone
Problems

React useOnClickOutside Hook

The useOnClickOutside hook provides a convenient way to detect when a React component loses focus due to a click outside of its boundaries. This is particularly useful for managing dropdown menus, modals, or any component that should close or hide when the user clicks elsewhere on the page. This challenge asks you to implement this hook, ensuring it correctly detects clicks outside a given element and executes a provided callback.

Problem Description

You are tasked with creating a custom React hook called useOnClickOutside. This hook accepts a callback function as an argument. When an element (identified by a ref passed to the hook) is clicked outside of, the callback function should be executed. The hook should also handle scenarios where the element is initially focused and then clicked outside.

What needs to be achieved:

  • Create a reusable React hook that detects clicks outside of a specified DOM element.
  • The hook should accept a callback function to be executed when a click outside the element is detected.
  • The hook should properly manage the event listener lifecycle, adding it when the component mounts and removing it when the component unmounts.

Key Requirements:

  • The hook must accept a ref object as its argument. This ref will be attached to the DOM element you want to monitor for clicks outside.
  • The hook must accept a callback function as its argument. This function will be executed when a click occurs outside the element referenced by the ref.
  • The hook should prevent memory leaks by removing the event listener when the component unmounts.
  • The hook should work correctly even if the element is initially focused and then clicked outside.

Expected Behavior:

  • When the component mounts, the hook should attach a global click event listener to the document.
  • When a click event occurs on the document, the hook should check if the click target is within the element referenced by the ref.
  • If the click target is not within the element, the provided callback function should be executed.
  • When the component unmounts, the hook should remove the global click event listener.

Important Edge Cases to Consider:

  • Initial Focus: The element might already have focus when the component mounts. The hook should still function correctly in this scenario.
  • Nested Elements: The element referenced by the ref might contain other elements. The hook should only trigger the callback if the click occurs outside the entire element, not just outside its immediate children.
  • Unmounting: Ensure the event listener is removed when the component unmounts to prevent memory leaks.
  • Multiple Instances: The hook should work correctly if used multiple times within the same application, each with its own ref and callback.

Examples

Example 1:

Input: A component with a div element referenced by a ref, and a callback function that logs a message to the console.
Output: When the user clicks outside the div, the callback function is executed, and "Clicked outside!" is logged to the console.
Explanation: The hook correctly detects the click outside the div and triggers the callback.

Example 2:

Input: A component with a modal that is initially open and focused.
Output: When the user clicks outside the modal, the modal closes (or its closing logic is triggered).
Explanation: The hook detects the click outside the focused modal and triggers the callback, which in turn closes the modal.

Example 3: (Edge Case - Unmounting)

Input: A component that mounts, attaches the event listener, and then unmounts before a click outside occurs.
Output: No error is thrown, and no memory leak occurs.
Explanation: The event listener is properly removed during unmounting, preventing issues.

Constraints

  • The hook must be written in TypeScript.
  • The hook should not rely on any external libraries beyond React.
  • The callback function should be executed asynchronously (e.g., using setTimeout with a 0ms delay) to avoid potential race conditions.
  • The hook should be performant and avoid unnecessary re-renders.

Notes

  • Consider using document as the event target for the click listener.
  • The ref object passed to the hook should be a React ref created using useRef().
  • Think about how to efficiently determine if a click target is within the element referenced by the ref. You can use element.contains(target) for this.
  • Remember to handle the event listener lifecycle correctly to prevent memory leaks.
  • The callback function should receive no arguments.
Loading editor...
typescript