I have a reusable component called DropDown and a custom hook called useClickOutside. I am using context API to track multiple dropdowns using unique ids.
Code for useClickOutside
import { useEffect, useRef } from "react";
import { toUSVString } from "util";
export function useClickOutside(callback: () => void) {
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
function handleClick(event: MouseEvent) {
const target = event.target as HTMLDivElement | HTMLButtonElement;
if (event.target === ref.current) {
return;
}
if (target?.contains(ref.current) && target !== ref.current) {
console.log("clicked outside");
callback();
}
}
window && window.addEventListener("click", handleClick, { capture: true });
return () => {
window &&
window.removeEventListener("click", handleClick, { capture: true });
};
}, [callback, ref.current]);
return ref;
}
code for DropDown component
import { ReactNode } from "react";
import { useClickOutside } from "../../util/hooks/useClickOutside";
export default function DropDown({
id,
onOpen,
onClose,
isOpen,
content,
buttonContent,
}: {
id: string;
onOpen: (id: string) => void | undefined;
onClose: () => void | undefined;
isOpen: boolean;
content?: ReactNode;
buttonContent?: any;
}) {
let elementRef = useClickOutside(() => {
if (isOpen) {
onClose();
}
});
return (
<div className="relative z-30">
<button
className="rounded-full p-0 border-2 border-slate-300"
onClick={() => (isOpen ? onClose() : onOpen(id))}
>
{buttonContent}
</button>
{isOpen && (
<div
ref={elementRef}
className="absolute space-y-2 right-0 w-56 mt-2 origin-top-right bg-white rounded-lg shadow-xl ring-1 ring-black ring-opacity-5 focus:outline-none z-30"
>
{content}
</div>
)}
</div>
);
}
Discussion on Nextjs Repository Loom with demo
Current Behavior DropDown close when I click outside the element but within the component that wraps around the DropDown component.
Expected Behavior I want the DropDown to close no matter where I click on the entire screen. Whether I click within the wrapping component or any other components, the event should be fired.
You are explicitly saying that you don't want to call the callback when clicking other elements, but what you need to do is, simply skip calling the callback when it's not necessary.
// useClickOutside.ts
import { useEffect, useRef } from "react";
export function useClickOutside(callback: () => void) {
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
function handleClick(event: MouseEvent) {
const el = ref?.current;
// Do nothing if clicking ref's element or descendent elements
if (!el || el.contains(event.target as Node)) {
return;
}
callback();
}
window?.addEventListener("click", handleClick, { capture: true });
return () => {
window?.removeEventListener("click", handleClick, { capture: true });
};
}, [callback, ref.current]);
return ref;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With