👋

【NextJs14】NextJs14 と 便利なライブラリ【#33Update Todo Server Actions】

2024/02/17に公開

【#33Update Todo Server Actions】

YouTube: https://youtu.be/1bp8neXOO9E

https://youtu.be/1bp8neXOO9E

今回はTodoのアップデートボタンを実装します。

動画の1:20秒当たりでスキーマのidの中身をコピーするのですが、
コピーではなくコードの移動をしていたので、
実際に作成する際にはコピーをお願いします。

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 { updateTodo } from "@/actions/todo-update";

import { Button } from "@/components/ui/button";

interface TodoActionsProps {
  todoId: string;
  isCompleted: boolean;
}

export const TodoActions = ({ todoId, isCompleted }: TodoActionsProps) => {
  const [isPending, startTransition] = useTransition();

  const onUpdate = () => {
    startTransition(() => {
      updateTodo({ id: todoId, isCompleted: isCompleted })
        .then((data) => toast.success(data.success))
        .catch((data) => {
          if (data.error) {
            toast.error(data.error);
          } else {
            toast.error("Something went wrong!");
          }
        });
    });
  };

  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
        onClick={onUpdate}
        disabled={isPending}
        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>
  );
};
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",
  }),
});

export const UpdateTodoSchema = z.object({
  id: z.string({
    required_error: "Id is required",
    invalid_type_error: "Id is required",
  }),
  isCompleted: z.boolean({
    required_error: "IsCompleted is required",
    invalid_type_error: "IsCompleted is required",
  }),
});
actions/todo-update.ts
"use server";
import * as z from "zod";
import { UpdateTodoSchema } from "@/types/schema";
import { db } from "@/lib/db";
import { revalidatePath } from "next/cache";
import { auth } from "@clerk/nextjs";

export const updateTodo = async (values: z.infer<typeof UpdateTodoSchema>) => {
  const { userId } = auth();

  if (!userId) {
    return {
      error: "Unauthorized",
    };
  }

  const validatedFields = UpdateTodoSchema.safeParse(values);

  if (!validatedFields.success) {
    return {
      error: "Invalid fields",
    };
  }

  const { id, isCompleted } = validatedFields.data;

  await db.todo.update({
    where: {
      id,
    },
    data: {
      isCompleted: !isCompleted,
    },
  });

  revalidatePath("/account");

  return { success: "Todo updated!" };
};

Discussion