I have a problem. I added an onClick event to an element. Its event handler's work is to change a state. After that state changed, I display a popup. I get access to that popup using useRef hook. Then I add a click event to the document and its event handler work is to check that user clicked outside the popup or not.
But the problem is here: when user clicks on the element, immediately the added event handler to the document will be executed! But how? look at these steps and you'll understand my point better:
user clicked on the show popup button --> onClick event handler executed --> the state changed --> an other event added to the document(for outside click purpose) --> immediately the event handler of click event on document executed (all these happen when you click on show popup button just once!!).
Option popup component:
import { useRef } from "react";
import useAxis from "../../hooks/useAxis";
import useOutSideClick from "../../hooks/useOutSideClick";
const Options = (props) => {
const { children, setShowOptions, showOptions } = props;
const boxRef = useRef();
const { childrens, offset } = useAxis(children, {
/* add an onClick to this components children(it has just one child and it is the open
popup button)*/
onClick: () => {
console.log("test1");
//show the options popup
setShowOptions(!showOptions);
},
});
//close the popup if user clicked outside the popup
useOutSideClick(boxRef, () => {
console.log("test2");
//close the popup
setShowOptions((prevState) => !prevState);
}, showOptions);
return (
<>
{showOptions && (
<div
ref={boxRef}
className="absolute rounded-[20px] bg-[#0d0d0d] border border-[#1e1e1e] border-solid w-[250px] overflow-y-auto h-[250px]"
style={{ left: offset.x + 25 + "px", top: offset.y + "px" }}
></div>
)}
{childrens}
</>
);
};
export default Options;
useOutSideClick custom hook:
import { useEffect } from "react";
//a custom hook to detect that user clicked
const useOutSideClick = (ref, outSideClickHandler, condition = true) => {
useEffect(() => {
if (condition) {
const handleClickOutside = (event) => {
console.log("hellloooo");
//if ref.current doesnt contain event.target it means that user clicked outside
if (ref.current && !ref.current.contains(event.target)) {
outSideClickHandler();
}
};
document.addEventListener("click", handleClickOutside);
return () => {
document.removeEventListener("click", handleClickOutside);
};
}
}, [ref, condition]);
};
export default useOutSideClick;
useAxis custom hook:
import React, { useEffect, useRef, useState } from "react";
const useAxis = (children, events) => {
const childRef = useRef();
const [offset, setOffset] = useState({
x: "",
y: "",
});
useEffect(() => {
Object.keys(events).forEach((event) => {
let eventHandler;
let callBack = events[event];
if (event === "onClick" || event === "onMouseEnter") {
eventHandler = (e) => {
callBack();
};
events[event] = eventHandler;
}
});
}, [JSON.stringify(events)]);
//setting mouse enter and leave event for the components children
const childrens = React.Children.map(children, (child) => {
return React.cloneElement(child, {
...events,
ref: childRef,
});
});
//initialize the position of child component at the first render
useEffect(() => {
setOffset(() => {
return {
x: childRef.current.offsetLeft,
y: childRef.current.offsetTop,
};
});
}, []);
return { childrens, offset };
};
export default useAxis;
the button(actually the element) component:
//basic styles for icons
const iconsStyles = "w-[24px] h-[24px] transition duration-300 cursor-pointer";
const NavBar = () => {
const [showOptions, setShowOptions] = useState(false);
return (
<Options
setShowOptions={setShowOptions}
showOptions={showOptions}
>
//onClick will be applied to this div only
<div
>
<TooltipContainer tooltipText="options">
<div>
<DotsHorizontalIcon
className={`${iconsStyles} ${
showOptions
? "text-[#fafafa]"
: "text-[#828282] hover:text-[#fafafa]"
}`}
/>
</div>
</TooltipContainer>
</span>
</Options>
//there are some other items that theres no need to be here
);
};
export default NavBar;
You can see my codes and the part of my app that you need to see in this CodeSandbox link.
so what should I do to avoid executing the event handler(for outside click purpose) immediately after the event added to the document by clicking on open popup button for the first time?