如何处理react ts中的外部点击

问题描述 投票:0回答:1

我第一次使用React TypeScript,并且我实现了外部点击事件。我遇到了一个问题,每当我单击活动或当前选定的组件时,它似乎都会触发 onclick 和外部单击事件。

附上 GIF,以便更好地理解我的问题。

enter image description here

当单击所选组件时,它会设置为活动状态,当我再次单击它时,它似乎会触发外部单击,然后触发单击事件。

这是我的代码实现。

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 作为弹出窗口

reactjs react-tsx
1个回答
0
投票

在针对该问题实现了各种方法之后,我通过在父组件上实现 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放在父组件中,确保它只触发一次。

© www.soinside.com 2019 - 2024. All rights reserved.