🐡
【NextJs14】NextJs14 と 便利なライブラリ【#32Delete Todo Server Actions】
【#32Delete Todo Server Actions】
YouTube: https://youtu.be/Sag4KlWUxwQ
今回はTodoの削除ボタンを実装します。
まずはボタンをクライアントサイドのコンポーネントに移動します。
app/(main)/account/_components/todo-actions.tsx
"use client";
import { useTransition } from "react";
import { toast } from "sonner";
import { CheckCircle, Trash2 } from "lucide-react";
import { deleteTodo } from "@/actions/todo-delete";
import { Button } from "@/components/ui/button";
interface TodoActionsProps {
todoId: string;
isCompleted: boolean;
}
export const TodoActions = ({ todoId, isCompleted }: TodoActionsProps) => {
const [isPending, startTransition] = useTransition();
const onDelete = () => {
startTransition(() => {
deleteTodo({ id: todoId })
.then((data) => toast.success(data.success))
.catch((data) => {
if (data.error) {
toast.error(data.error);
} else {
toast.error("Something went wrong!");
}
});
});
};
return (
<div className="flex items-center gap-x-2">
<Button variant={isCompleted ? "Completed" : "secondary"}>
<CheckCircle className="h-5 w-5" />
</Button>
<Button onClick={onDelete} disabled={isPending} variant="destructive">
<Trash2 className="h-5 w-5" />
</Button>
</div>
);
};
app/(main)/account/page.tsx
import { formatDistanceToNow } from "date-fns";
import { db } from "@/lib/db";
import { Separator } from "@/components/ui/separator";
import { TodoForm } from "./_components/todo-form";
import { TodoActions } from "./_components/todo-actions";
const getTodos = async () => {
const todos = await db.todo.findMany({
orderBy: {
createdAt: "asc",
},
});
return todos;
};
const AccountPage = async () => {
const todos = await getTodos();
return (
<div className="p-6 w-full h-full">
<div className="w-full h-full flex flex-col gap-y-3">
<div className="flex flex-col md:flex-row items-center justify-between gap-2">
<h2 className="text-center md:text-left text-3xl font-bold">Todos</h2>
<TodoForm />
</div>
<Separator />
<ul className="w-full space-y-3">
{todos.map((todo, idx) => (
<li
key={todo.id}
className="flex flex-col md:flex-row items-center justify-between gap-x-4 gap-y-2"
>
<div className="flex flex-col md:flex-row flex-1 items-center justify-between gap-x-3">
<p className="text-xl font-semibold">
<span className="pr-2">{idx + 1}</span>
<span className={todo.isCompleted ? "line-through" : ""}>
{todo.title}
</span>
</p>
<p>
{formatDistanceToNow(todo.createdAt, { addSuffix: true })}
</p>
</div>
<TodoActions todoId={todo.id} isCompleted={todo.isCompleted} />
</li>
))}
</ul>
</div>
</div>
);
};
export default AccountPage;
types/schema.ts
import * as z from "zod";
export const CreateTodoSchema = z.object({
title: z.string().min(1, {
message: "Title is required.",
}),
});
export const DeleteTodoSchema = z.object({
id: z.string({
required_error: "Id is required",
invalid_type_error: "Id is required",
}),
});
actions/todo-delete.ts
"use server";
import * as z from "zod";
import { DeleteTodoSchema } from "@/types/schema";
import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";
import { auth } from "@clerk/nextjs";
export const deleteTodo = async (values: z.infer<typeof DeleteTodoSchema>) => {
const { userId } = auth();
if (!userId) {
return {
error: "Unauthorized",
};
}
const validatedFields = DeleteTodoSchema.safeParse(values);
if (!validatedFields.success) {
return {
error: "Invalid fields",
};
}
const { id } = validatedFields.data;
await db.todo.delete({
where: {
id,
},
});
revalidatePath("/account");
return { success: "Todo deleted!" };
};
idの渡し方につきましては、
ボタンをフォームでラップして、インプットタグのvalueにidを設定する方法や、
以下のドキュメントのような方法があります。
Discussion