我第一次使用React TypeScript,并且我实现了外部点击事件。我遇到了一个问题,每当我单击活动或当前选定的组件时,它似乎都会触发 onclick 和外部单击事件。
附上 GIF,以便更好地理解我的问题。
当单击所选组件时,它会设置为活动状态,当我再次单击它时,它似乎会触发外部单击,然后触发单击事件。
这是我的代码实现。
import { Button } from "@/components/ui/button";
import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarTrigger,
} from "@/components/ui/menubar";
import { MapPin, MoreVertical, PinOff } from "lucide-react";
import { HtmlHTMLAttributes, useState } from "react";
import OutsideClick from "outsideclick-react";
import { Props } from "@/utilities/navigation_props";
interface DocumentCardProps extends Props, HtmlHTMLAttributes<HTMLDivElement> {
data: DateType,
onSelectActive: (id: number|null) => void,
}
type DateType = {
id: number
}
const DocumentCard = ({
data,
active,
onSelectActive,
...props
}: DocumentCardProps) => {
return (
<OutsideClick
onOutsideClick={() => {
onSelectActive(null)
console.log(active)
}}
ignoreElement={[".menubarEl"]}
className="pointer-events-none"
onClick={(e) => {
e.stopPropagation()
onSelectActive(data.id)
}}
>
<div
{...props}
className={`h-56 p-2 flex flex-col rounded-xl cursor-default pointer-events-auto
${active == data.id ? "dark:bg-green-400/50 bg-green-400/50" : "hover:bg-tertiary"}`}
>
<div className="bg-tertiary grow rounded-lg overflow-hidden pointer-events-none">
<img src="https://picsum.photos/400" alt="" className="" />
</div>
<div className="shrink-0 py-2 text-sm font-medium px-0.5 flex items-center">
<div className="grow">
<div className={`${active == data.id ? "dark:text-emerald-200" : ""}`}>
Document title
</div>
<div
className={`opacity-70 font-normal text-xs ${active == data.id ? "dark:text-emerald-300" : ""
}`}
>
Document{">"}path{">"}name
</div>
</div>
<div>
<Menubar className="!border-none !bg-transparent !p-0">
<MenubarMenu>
<MenubarTrigger className="!px-0 focus:!bg-transparent data-[state=open]:!bg-transparent !p-0 !rounded-full ring-1">
<Button
asChild
variant={"ghost"}
size={"iconxs"}
className={`hover:dark:border-white/10 !rounded-full border hover:border-gray-300/60 dark:hover:bg-white/5 border-transparent`}
>
<div>
<MoreVertical className="h-4 w-4" />
</div>
</Button>
</MenubarTrigger>
<MenubarContent className="ring-1 ring-inset menubarEl" align="end" alignOffset={20} sideOffset={-15}>
<MenubarItem>
<PinOff className="w-5 h-5" strokeWidth={1.7} />
<div className="ml-3">Unpin</div>
</MenubarItem>
<MenubarItem>
<MapPin className="w-5 h-5" strokeWidth={1.7} />
<div className="ml-3">Track</div>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</div>
</div>
</div>
</OutsideClick>
);
};
function Pinned() {
const [active, setActive] = useState<number|null>(null)
const documents = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 6 },
{ id: 7 },
];
return (
<div className="grid grid-cols-[repeat(auto-fill,minmax(17rem,1fr))] gap-1">
{documents.map((item, index) => (
<DocumentCard
key={index}
data={item}
active={active}
onSelectActive={(id) => {
setActive(id)
}}
/>
))}
</div>
);
}
export default Pinned;
我正在使用 Outsideclick-react,并使用 shadcn 作为弹出窗口
在针对该问题实现了各种方法之后,我通过在父组件上实现 externalclick 提出了一个解决方案。我注意到,当我在 DocumentCard 组件外部单击时,其他 DocumentCard 组件会触发 externalclick,从而导致问题。这是我所做的修改:
import { Button } from "@/components/ui/button";
import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarTrigger,
} from "@/components/ui/menubar";
import { MapPin, MoreVertical, PinOff } from "lucide-react";
import { HtmlHTMLAttributes, useEffect, useState } from "react";
import OutsideClick from "outsideclick-react";
import { Props } from "@/utilities/navigation_props";
interface DocumentCardProps extends Props, HtmlHTMLAttributes<HTMLDivElement> {
data: DateType;
onSelectActive: (id: number | null) => void;
onClickMenu: () => void;
}
type DateType = {
id: number;
};
const DocumentCard = ({
data,
active,
onClickMenu,
onSelectActive,
...props
}: DocumentCardProps) => {
return (
<div
{...props}
onClick={() => {
onSelectActive(data.id);
}}
className={`h-56 p-2 flex flex-col rounded-xl cursor-default
${active == data.id
? "dark:bg-green-400/50 bg-green-400/50"
: "hover:bg-tertiary"
}`}
>
<div className="bg-tertiary grow rounded-lg overflow-hidden pointer-events-none">
<img src="https://picsum.photos/400" alt="" className="" />
</div>
<div className="shrink-0 py-2 text-sm font-medium px-0.5 flex items-center">
<div className="grow">
<div
className={`${active == data.id ? "dark:text-emerald-200" : ""}`}
>
Document title
</div>
<div
className={`opacity-70 font-normal text-xs ${active == data.id ? "dark:text-emerald-300" : ""
}`}
>
Document{">"}path{">"}name
</div>
</div>
<div id={"menubar-trigger-" + data.id}>
<Menubar className="!border-none !bg-transparent !p-0">
<MenubarMenu>
<MenubarTrigger
className="!px-0 focus:!bg-transparent data-[state=open]:!bg-transparent !p-0 !rounded-full"
>
<Button
asChild
variant={"ghost"}
size={"iconxs"}
className={`hover:dark:border-white/10 !rounded-full border hover:border-gray-300/60 dark:hover:bg-white/5 border-transparent`}
>
<div>
<MoreVertical className="h-4 w-4" />
</div>
</Button>
</MenubarTrigger>
<MenubarContent
className=" menubarContainer"
align="end"
alignOffset={2}
sideOffset={2}
>
<MenubarItem className="menubarItem" onClick={() => onClickMenu()}>
<PinOff className="w-5 h-5" strokeWidth={1.7} />
<div className="ml-3">Unpin</div>
</MenubarItem>
<MenubarItem className="menubarItem" onClick={() => onClickMenu()}>
<MapPin className="w-5 h-5" strokeWidth={1.7} />
<div className="ml-3">Track</div>
</MenubarItem>
</MenubarContent>
</MenubarMenu>
</Menubar>
</div>
</div>
</div>
);
};
function Pinned() {
const [active, setActive] = useState<number | null>(null);
const documents = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 6 },
{ id: 7 },
];
useEffect(() => {
const onOutsideClick = (e: MouseEvent) => {
const element = document.getElementById("document-card-" + active);
const menubar = document.querySelector(".menubarContainer");
const menubarTrigger = document.getElementById(
"menubar-tigger-" + active
);
const menubarItem = document.querySelector(".menubarItem");
if (
active &&
!element?.contains(e.target as Node) &&
!menubar?.contains(e.target as Node) &&
!menubarItem?.contains(e.target as Node) &&
!menubarTrigger?.contains(e.target as Node)
) {
setActive(null);
}
console.log(e.target)
};
document.addEventListener("mousedown", onOutsideClick);
return () => document.removeEventListener("mousedown", onOutsideClick);
}, [active]);
return (
<div className="grid grid-cols-[repeat(auto-fill,minmax(17rem,1fr))] gap-1">
{documents.map((item, index) => (
<DocumentCard
key={index}
data={item}
active={active}
id={"document-card-" + item.id}
onSelectActive={(id) => {
setActive(id);
}}
onClickMenu={() => console.log('clickedMenu')}
/>
))}
</div>
);
}
export default Pinned;
所以,修改后的实现是将outsideclick放在父组件中,确保它只触发一次。