🔖
【Convex】NextJs14 と Convex【#31 Convex stopPropagation】
【#31 Convex stopPropagation】
YouTube: https://youtu.be/76980tsyICA
今回はカードに詳細ページ用のリンクを
設定した際に発生する問題について解説します。
まずは詳細ページを作成します。
app/(main)/[cardId]/page.tsx
interface Props {
params: {
cardId: string;
};
}
const CardIdPage = ({ params }: Props) => {
return (
<>
<h2>Card Id Page</h2>
<div>{params.cardId}</div>
</>
);
};
export default CardIdPage;
次にカードにリンクを設定します。
app/(main)/_components/card/index.tsx
"use client";
import Link from "next/link";
import Image from "next/image";
import { useAuth } from "@clerk/nextjs";
import { formatDistanceToNow } from "date-fns";
import { Footer } from "./footer";
import { Overlay } from "./overlay";
import { CardActions } from "./card-actions";
import { MoreHorizontal } from "lucide-react";
interface Props {
id: string;
title: string;
imageUrl: string;
authorId: string;
authorName: string;
createdAt: number;
orgId: string;
cardType: string;
}
export const Card = ({
id,
title,
imageUrl,
authorId,
authorName,
createdAt,
orgId,
cardType,
}: Props) => {
const { userId } = useAuth();
const authorLabel = userId === authorId ? "You" : authorName;
const createdAtLabel = formatDistanceToNow(createdAt, { addSuffix: true });
return (
<Link href={`/${id}`}>
<div className="group aspect-[100/128] border rounded-lg flex flex-col justify-between overflow-hidden">
<div className="relative flex-1 bg-slate-300">
<Image src={imageUrl} alt={title} fill className="object-cover" />
<Overlay />
<CardActions id={id} title={title}>
<button className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity px-3 py-2 outline-none">
<MoreHorizontal className="text-white opacity-75 hover:opacity-100 transition-opacity" />
</button>
</CardActions>
</div>
<Footer
title={title}
authorLabel={authorLabel}
type={cardType}
createdAtLabel={createdAtLabel}
/>
</div>
</Link>
);
};
最後に「DropdownMenuContent」のonClickに「stopPropagation」を追加します。
app/(main)/_components/card/card-actions.tsx
"use client";
import { Pencil, Trash2 } from "lucide-react";
import { toast } from "sonner";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useRenameCardModal } from "@/store/use-rename-card-modal";
import { useApiMutation } from "@/hooks/use-api-mutation";
import { api } from "@/convex/_generated/api";
import { Button } from "@/components/ui/button";
import { ConfirmModal } from "@/components/confirm-modal";
interface Props {
children: React.ReactNode;
id: string;
title: string;
}
export const CardActions = ({ children, id, title }: Props) => {
const { onOpen } = useRenameCardModal();
const { pending, mutate } = useApiMutation(api.card.deleteCard);
const onDelete = () => {
mutate({ id })
.then(() => toast.success("Card deleted"))
.catch(() => toast.error("Failed to delete card"));
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent onClick={(e) => e.stopPropagation()}>
<DropdownMenuItem
className="p-3 cursor-pointer"
onClick={() => onOpen(id, title)}
>
<Pencil className="h-4 w-4 mr-2" /> Rename
</DropdownMenuItem>
<ConfirmModal
header="Are you sure you want to delete card?"
description="All contents on the card will be deleted and cannot undone."
disabled={pending}
onConfirm={onDelete}
>
<Button
variant="ghost"
className="p-3 cursor-pointer text-sm w-full justify-start font-normal"
>
<Trash2 className="h-4 w-4 mr-2" /> Delete
</Button>
</ConfirmModal>
</DropdownMenuContent>
</DropdownMenu>
);
};
Discussion