In the DOM, events propagate in two phases: the capturing phase and the bubbling phase. By default, React event handlers are attached in the bubbling phase. When a user clicks on an element, the click event starts from the innermost element and bubbles up through its ancestors in the DOM tree.
React refs provide a way to access DOM nodes or React elements created in the render method. We can use refs to get a reference to the element for which we want to detect outside clicks.
TypeScript adds static typing to JavaScript, which helps catch errors early in the development process. When working with React and DOM elements, TypeScript allows us to define the types of our variables and function parameters more precisely.
useRef
Hook and useEffect
HookThe useRef
hook is used to create a mutable ref object that persists across renders. The useEffect
hook is used to perform side effects in functional components.
import React, { useRef, useEffect } from 'react';
const ClickOutsideExample: React.FC = () => {
const ref = useRef<HTMLDivElement>(null);
const handleClickOutside = (event: MouseEvent) => {
if (ref.current &&!ref.current.contains(event.target as Node)) {
console.log('Clicked outside the element');
}
};
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
return (
<div ref={ref} style={{ border: '1px solid black', padding: '10px' }}>
This is the element.
</div>
);
};
export default ClickOutsideExample;
In this example, we create a ref using the useRef
hook and attach it to the div
element. In the handleClickOutside
function, we check if the click event target is outside the element referenced by ref
. The useEffect
hook adds a mousedown
event listener to the document
when the component mounts and removes it when the component unmounts.
To make the code more reusable, we can wrap the click outside detection logic in a custom hook.
import React, { useRef, useEffect } from 'react';
const useClickOutside = <T extends HTMLElement>(callback: () => void) => {
const ref = useRef<T>(null);
const handleClickOutside = (event: MouseEvent) => {
if (ref.current &&!ref.current.contains(event.target as Node)) {
callback();
}
};
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [callback]);
return ref;
};
const ClickOutsideExampleWithHook: React.FC = () => {
const handleOutsideClick = () => {
console.log('Clicked outside the element');
};
const ref = useClickOutside<HTMLDivElement>(handleOutsideClick);
return (
<div ref={ref} style={{ border: '1px solid black', padding: '10px' }}>
This is the element.
</div>
);
};
export default ClickOutsideExampleWithHook;
In this example, the useClickOutside
custom hook takes a callback function as an argument and returns a ref. The callback function is called when a click outside the element is detected.
document
, you can attach it to a closer ancestor element if possible. This can reduce the number of events that need to be processed.Escape
key, to close the dropdown or modal.Detecting clicks outside an element in React with TypeScript is a common requirement in many applications. By understanding the fundamental concepts of event propagation and React refs, and using the useRef
and useEffect
hooks, we can implement this functionality effectively. Wrapping the logic in a custom hook makes the code more reusable, and following best practices ensures better performance and accessibility.