🔖

【Convex】NextJs14 と Convex【#31 Convex stopPropagation】

2024/06/04に公開

【#31 Convex stopPropagation】

YouTube: https://youtu.be/76980tsyICA
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