こんにちは。 アドベントカレンダー22日目です。終盤に差し掛かってきました。 今年はフロントエンドまわりであれこれ開発することが多かったので、振り返りを兼ねてこれからフロントエンド開発を始める方向けに入門編としてお送りしています。 最終日の25日には何かアプリケーションができていることが目標です。
↓最初から読み始めたい方はこちらか↓
おさらい
- Visual Studio Codeを使ったReactの開発環境の構築(第1回)
- CSSフレームワークの1つであるtailwindcssを使ったUI開発(第2回)
- コンポーネント化(第3回)
- フォーマッター(第4回)
- Linter(第5回)
- バージョン管理(第6回)
- 状態管理とReact hook form(第7回、第8回、第11回))
- Visual Studio Codeの設定(第9回、第10回、第16回、第17回)
- Reactルーティング(第12回)
- Playwright(第13回、第14回)
- GitHub CodespacesでAmplify
- 環境立ち上げとCLIツールの有効化(第15回)
- Amplifyで初期プロジェクト作成(第18回)
- Amplifyで認証機能作成(第19回)
- AmplifyでAPI機能作成(第20回)
- Amplifyで書籍登録機能(第21回)
今回のはなし
第18回からAmplifyシリーズでお送りしています。「書籍管理アプリ」を作りながらAPI機能の基本的な使い方を学んでいます。前回は、書籍登録の処理を作ってみました。今回は登録した書籍を一覧で閲覧できるように取得機能を作ってみたいと思います。
準備
/pages/index.tsx
のファイルを使って作成します。これまでの記事であれこれ書いてきましたが、その内容は1度リセットします。
$ tree ./pages/ ./pages/ ├── app.css ├── _app.tsx ├── book │ └── resister.tsx └── index.tsx
画面UIの作成
まずは画面UIから作成します。今回は書籍件数に応じて動的に表示する部分がありますが全体的なレイアウトを決めるため、静的な形で作成します。
サンプルコードはこちら
"use client";
export default function Home() {
return (
<div className="space-y-5">
<h3 className="text-3xl font-bold dark:text-white">書籍一覧</h3>
<div>
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
書籍を登録する
</button>
</div>
<div>
<table className="border-collapse border border-slate-400 table-auto">
<thead>
<tr>
<td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">ID</td>
<td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">ISBN</td>
<td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">書籍名</td>
<td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">著者</td>
<td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">出版社</td>
</tr>
</thead>
<tbody>
<tr>
<td className="border border-slate-300 p-2">xxxxxxxxxxxxxxxxxxxxx</td>
<td className="border border-slate-300 p-2">9999999999999</td>
<td className="border border-slate-300 p-2">これから始める人のためのDevOps超入門</td>
<td className="border border-slate-300 p-2">田中 太郎</td>
<td className="border border-slate-300 p-2">日本仮想化技術出版</td>
</tr>
</tbody>
</table>
</div>
</div>
);
}
コードを実行したときに表示される画面はこんな感じです。
取得処理の作成
画面UIの準備ができたら次は取得処理を実装してみましょう。GraphQLでは取得方法はget
とlist
の2パターンあります。前者のget
は、IDを指定して1件のデータを取得できます。後者のlist
は、複数件のデータをまとめて取得できます。今回は一覧表示をしたいので、list
を使って表示してみます。
fetchBooks
という関数を1つ作ります。関数の中ではGraphQLを通じてDynamoDBにデータを取得する処理を記述しています。
./pages/fetchBooks.ts
というファイルを作成して、次のコードを貼り付けます。
import { Book, ListBooksQuery } from "@/src/API"; import { listBooks } from "@/src/graphql/queries"; import { GraphQLResult, generateClient } from "aws-amplify/api"; export default async function fetchBooks() { const client = generateClient(); const response = await client.graphql({ query: listBooks, }) as GraphQLResult<ListBooksQuery>; if (response.errors) { throw new Error(response.errors[0].message); } if (!response.data?.listBooks) { throw new Error("Book not created"); } const books: Book[] = response.data?.listBooks.items .filter((item): item is NonNullable<typeof item> => !!item) .map((item) => item); return books; }
メインの処理はこの部分ですが、データ登録時と部分的に少し変わっただけで、基本的なフォーマットは同じです。
const client = generateClient(); const response = await client.graphql({ query: listBooks, }) as GraphQLResult<ListBooksQuery>;
それ以降の処理に関しては、レスポンス内容を取り扱いしやすいようにあれこれ加工しているところになります。今回はそこまで重要な処理ではないため、省略します。
データ取得するための関数の準備ができましたが、今回はカスタムフックというものを作ります。データ操作系をまとめておくとスッキリするので、筆者的にはこのような形でまとめています。loadingなどを用意しておくと画面描画時に読み込み中のアイコンを表示したりする際に活用できます。
hooks/useBook.ts
というファイルを作成して、次のコードを貼り付けてください。
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); }); }, []); return { books, loading, error }; }
useEffect
はこれまでの記事で触れていないですがざっくりと言ってしまうと、指定した値が変更される度に引数で渡した関数を実行してくれるものです。useState
と組み合わせてよく使用されます。
最後に作成したカスタムフックを./pages/index.tsx
で呼び出してみましょう。
"use client"; + import useBook from "@/hooks/useBook"; + import Link from "next/link"; export default function Home() { + const { books, loading, error } = useBook(); + if (loading) { + return <div>データを読み込んでいます...</div>; + } + if (error) { + return <div>エラーが発生しました</div>; + } 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"> <thead> <tr> <td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">ID</td> <td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">ISBN</td> <td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">書籍名</td> <td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">著者</td> <td className="border border-slate-300 p-2 text-center font-bold bg-slate-50">出版社</td> </tr> </thead> <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> + </tr> + ))} </tbody> </table> </div> </div> ); }
カスタムフックを使いときは、const { ... } = useBooks();
のような形で呼び出します。books
には取得できた書籍情報の一覧が格納されており、loading
に現在処理中かどうかのステータス保持されています。
書籍一覧を表示するときには{books.map((book) => ( ... ))}
のような形でループして1件ずつ表示すると次の画像のように表示されます。
データ取得編はこれで以上です。
おわりに
前回作成したデータを表示できるようにするために、今回はデータを取得する処理を実装しました。これで、作成したデータを確認することができるようになりました。次回は、編集編をお届けします。