Next.js 14でTodoアプリを作る – 7.データ一覧表示

投稿日:2024年03月15日

更新日:2024年03月15日

投稿者:HIROKI CHIYODA

一覧表の作成

取得した全件データを表示する一覧表を作成します。
まずはテーブルtodosに登録したデータも直接入力しています。
以下のコードのように<main>と変更します。

<main className="text-center mx-auto max-w-2xl space-y-8">
  <h1 className="text-3xl">Todo App</h1>
  <table className="w-full">
    <thead className="border-b-2 border-black/60">
      <tr>
        <th className="w-1/4 border-r-2 border-black/60">タイトル</th>
        <th className=" w-3/4">内容</th>
      </tr>
    </thead>
    <tbody>
      <tr className="border-b border-black">
        <td className="w-1/4 border-r-2 border-black/60">タスク1</td>
        <td className="w-3/4">これはタスク1です</td>
      </tr>
      <tr className="border-b border-black">
        <td className="w-1/4 border-r-2 border-black/60">タスク2</td>
        <td className="w-3/4">これはタスク2です</td>
      </tr>
      <tr className="border-b border-black">
        <td className="w-1/4 border-r-2 border-black/60">タスク3</td>
        <td className="w-3/4">これはタスク3です</td>
      </tr>
    </tbody>
  </table>
</main>

データの取得

データベースからテーブルtodosのデータを全件取得します。
Next.jsのドキュメントを参考にします。

https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-server-with-fetch

まずはテーブルtodosのデータを取得する関数getTodosを作成します。
以下のコードをファイルpage.tsxHomeコンポーネントの前に追加します。

async function getTodos() {
  const res = await fetch("https://todo-app-d1-base.pages.dev/api/todos");
  if (!res.ok) {
    throw new Error('Failed to fetch data')
  }
  return res.json()
}

次に関数getTodosを呼び出してデータを全件取得します。
まずは全件取得できたかどうかコンソールで確認します。
Homeコンポーネントのreturnの前に以下のコードを追加します。

const todos = await getTodos();
console.log(todos);

コンソールに取得したデータが表示されました。

[
  { id: 1, title: 'タスク1', content: 'これはタスク1です' },
  { id: 2, title: 'タスク2', content: 'これはタスク2です' },
  { id: 3, title: 'タスク3', content: 'これはタスク3です' }
]

データが1つのオブジェクトで全データが配列に格納されています。

全件取得したデータを表示

全件取得したデータを表に反映させます。
変数todosからmap関数で配列から変数todoに1つづつデータを取得します。
<table><tbody>の中を削除して以下のコードを追加します。

{todos.map((todo) => (
  <tr className="border-b border-black" key={todo.id}>
    <td className="w-1/4 border-r-2 border-black/60">{todo.title}</td>
    <td className="w-3/4">{todo.content}</td>
  </tr>
))}

これで一覧表に取得したデータが反映されました。
ただ、エラーが表示されています。
このままだと気持ち悪いので解決します。

エラーを解決

エラー内容を確認します。
エラーを確認するとtodostodoでエラーが発生しています。
todosのエラー内容は「todosはunknown型です。」とあります。
todoのエラー内容は「パラメーターtodoの型は暗黙的にanyになります。」とあります。
以上から変数todosに型が設定されていないのが問題のようです。

まずは型を定義します。
ディレクトりtypes内にファイルTodo.tsを作成します。
ファイルTodo.tsに型Todoを定義します。
以下のコードを追加します。

type Todo = {
  id: number;
  title: string;
  content: string;
};

次に変数todosに型を定義します。
具体的にはデータの取得元である関数getTodosの返り値に型を指定します。
関数getTodosの返り値res.json()に型Todoを指定します。
以下のように、コードを変更します。
Todoの後に[]をつけているのは、複数のオブジェクトが配列で格納されているからです。
取得したデータをコンソールで確かめたことを思い出してください。

return res.json<Todo[]>();

これでエラーが消えました。

一覧表のコンポーネントを作成

<table>の部分を切り取って一覧表のコンポーネントを作成します。
ルートディレクトリにcomponentsディレクトリを作成します。
componentsディレクトリ内にTodoTable.tsxを作成します。
rafceと入力してスペニットでコードを生成します。

const TodosTable = () => {
  return <div>TodosTable</div>;
};

export default TodosTable;

作成したコンポーネントの<div>page.tsx<table>の部分を貼り付けます。
map関数で使うtodosを引数で受け取りたいのでTodosPropsを定義します。

const TodosTable = ({ todos }: TodosProps) => {
  return (
    <table className="w-full">
      <thead className="border-b-2 border-black/60">
        <tr>
          <th className="w-1/4 border-r-2 border-black/60">タイトル</th>
          <th className=" w-3/4">内容</th>
        </tr>
      </thead>
      <tbody>
        {todos.map((todo) => (
          <tr className="border-b border-black" key={todo.id}>
            <td className="w-1/4 border-r-2 border-black/60">{todo.title}</td>
            <td className="w-3/4">{todo.content}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
};

export default TodosTable;

TodosPropstypesディレクトリのTodo.tsに追記します。

type TodosProps = {
  todos: Todo[];
};

page.tsxに切り取った<table>の代わりにTodosTableコンポーネントを追加します。

<main className="text-center mx-auto max-w-2xl space-y-8">
  <h1 className="text-3xl">Todo App</h1>
  <TodosTable todos={todos} />
</main>

TodosTableコンポーネントをインポートします。

import TodosTable from "@/components/TodosTable";

関数getTodosを別ファイルに移動

関数getTodosを別ファイルに移動します。
ルートディレクトリにutilsディレクトリを作成します。
utilsディレクトリ内にapi.tsを作成します。

export async function getTodos() {
  const res = await fetch("https://todo-app-d1-base.pages.dev/api/todos");
  if (!res.ok) {
    throw new Error("Failed to fetch data");
  }
  return res.json<Todo[]>();
}

asyncの前にexportを追加して、他のファイルから呼び出すようにします。
そしてpage.tsxに関数getTodosをインポートします。

import { getTodos } from "@/utils/api";