とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

Amplifyを使ってWebアプリを作ろう(書籍削除編)

こんにちは。 アドベントカレンダー24日目です。 今年はフロントエンドまわりであれこれ開発することが多かったので、振り返りを兼ねてこれからフロントエンド開発を始める方向けに入門編としてお送りしています。 最終日の25日には何かアプリケーションができていることが目標です。

↓最初から読み始めたい方はこちらか↓

devops-blog.virtualtech.jp

おさらい

今回のはなし

第18回からAmplifyシリーズでお送りしています。「書籍管理アプリ」を作りながらAPI機能の基本的な使い方を学んでいます。前回は、書籍更新の処理を作りました。今回は登録した書籍情報を削除できるように削除機能を追加していきましょう。

  pages
  ├── app.css
  ├── _app.tsx
  ├── book
  │   ├── createBookData.ts
  │   ├── [id]
  │   │   ├── edit.tsx
  │   │   ├── fetchbook.ts
+ │   │   └── updateBookData.ts
  │   └── resister.tsx
  ├── deleteBookData.ts
  ├── fetchBooks.ts
  └── index.tsx

まずは、削除するための関数を作ります。これまでと同じように部分的に異なるくらいで、ほとんと書き方はこれまでと同じようなものなので、細かい説明は省略します。

import { Book, DeleteBookMutation } from "@/src/API";
import { deleteBook } from "@/src/graphql/mutations";
import { GraphQLResult, generateClient } from "aws-amplify/api";

export default async function deleteBookData(id: string) {
  const client = generateClient();
  const response = await client.graphql({
    query: deleteBook,
    variables: { input: { id } },
  }) as GraphQLResult<DeleteBookMutation>;

  if (response.errors) {
    throw new Error(response.errors[0].message);
  }

  if (!response.data?.deleteBook) {
    throw new Error("Book not created");
  }

  const book: Book = response.data?.deleteBook;
  return book;
}

作成した削除関数はuseBook内で使用します。このように削除処理を記述してください。削除処理に成功した場合は、カスタムフック内で保持している配列から当該のデータを削除します。そすることで、画面上からの一覧からもリアルタイムにデータを消すことができます。

+ import deleteBookData from "@/pages/deleteBookData";
import fetchBooks from "@/pages/fetchBooks";
import { Book } from "@/src/API";
import { useEffect, useState } from "react";

export default function useBook() {
  const [books, setBooks] = useState<Book[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    fetchBooks()
      .then((books) => {
        setBooks(books);
      })
      .catch((error) => {
        setError(error);
      }).finally(() => {
        setLoading(false);
      });
  }, []);

+ const deleteBook = async (id: string) => deleteBookData(id)
+   .then((book) => {
+     setBooks((books) => books.filter((book) => book.id !== id));
+   }).catch((error) => {
+     throw error;
+   });

  return { books, loading, error, deleteBook };
}

カスタムフックの修正ができたらpages/index.tsxから先ほど作成したdelete関数を呼び出します。

...
export default function Home() {
  const {
    books,
    loading,
    error,
+   deleteBook
  } = useBook();

...

+ const handleDelete = async (id: string) => {
+   const confirm = window.confirm("本当に削除しますか?");
+   if (!confirm) {
+     return;
+   }
++   deleteBook(id)
+     .then(() => {
+       alert("削除しました");
+     }).catch(() => {
+       alert("削除に失敗しました");
+     });
+ }

  return (
    <div className="space-y-5">
      <h3 className="text-3xl font-bold dark:text-white">書籍一覧</h3>
      <div>
        <Link href="/book/resister" className="text-blue-500 hover:text-blue-700">
          書籍を登録する
        </Link>

      </div>
      <div>
        <table className="border-collapse border border-slate-400 table-auto">
          ...
          <tbody>
            {books.map((book) => (
              <tr>
                <td className="border border-slate-300 p-2">{book.id}</td>
                <td className="border border-slate-300 p-2">{book.isbn || "(なし)"}</td>
                <td className="border border-slate-300 p-2">{book.name}</td>
                <td className="border border-slate-300 p-2">
                  {book.auther?.filter((item): item is NonNullable<typeof item> => !!item).join(", ")}
                </td>
                <td className="border border-slate-300 p-2">{book.publisher}</td>
                <td className="border border-slate-300 p-2 space-x-2">
                  <Link
                    href={`/book/${encodeURIComponent(book.id)}/edit`}
                    className="text-blue-500 hover:text-blue-700"
                  >
                    編集
                  </Link>
+                 <button
+                   type="button"
+                   className="text-red-500 hover:text-red-700 whitespace-nowrap"
+                   onClick={() => handleDelete(book.id)}
+                 >
+                   削除
+                 </button>
+               </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

これで削除処理を追加することができました。

おわりに

これで一通りの機能は簡易的ですが、実装することができました。これで書籍管理アプリは完成です。思っていたよりも簡単に処理を作れることが実感できたのではないでしょうか。 最後に書籍情報を追加してファイルを