【发布时间】:2022-11-10 15:00:17
【问题描述】:
我有个问题。我向一个元素添加了一个onClick 事件。它的事件处理程序的工作是改变一个状态。在该状态更改后,我会显示一个弹出窗口。我可以使用useRef 钩子访问该弹出窗口。然后我向document 添加一个click 事件,它的事件处理程序的工作是检查用户是否在弹出窗口之外单击。
但问题就在这里:当用户点击元素时,添加到document 的事件处理程序将立即被执行!但是怎么做?看看这些步骤,你会更好地理解我的观点:
用户单击显示弹出按钮-> onClick 事件处理程序已执行--> 状态已更改--> 添加到文档中的另一个事件(用于外部单击目的)--> 立即执行文档上单击事件的事件处理程序(当您单击一次显示弹出按钮时,所有这些都会发生!!)。
选项弹出组件:
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 自定义钩子:
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;
使用Axios 自定义钩子:
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;
按钮(实际上是元素)组件:
//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;
您可以在此 CodeSandbox link 中查看我的代码和您需要查看的应用程序部分。
那么我应该怎么做才能避免在第一次单击打开弹出按钮将事件添加到document 后立即执行事件处理程序(用于外部单击目的)?
【问题讨论】:
标签: javascript reactjs react-hooks event-handling dom-events