Next.js 14でTodoアプリを作る – 8.データ新規登録

投稿日:2024年03月16日

更新日:2024年03月16日

投稿者:HIROKI CHIYODA

フォームの作成

登録するフォームを作成します。
<form>の中に<label><input>を順に2組作成します。
1つ目の<label>には「タイトル」を入力します。
2つ目の<label>には「内容」を入力します。

<form className="flex flex-col">
  <label>タイトル</label>
  <input type="text" className="border-2 rounded-lg p-1" />
  <label>内容</label>
  <input type="text" className="border-2 rounded-lg p-1" />
  <button
    type="submit"
    className="bg-black text-white rounded-lg w-full mt-4 p-1"
  >
    登録
  </button>
</form>

typescript:TodoInput.tsx

"use client";

import { RefObject } from "react";

const TodoInput = (props: {
  labelText: string;
  ref: RefObject<HTMLInputElement>;
}) => {
  return (
    <>
      <label>{props.labelText}</label>
      <input ref={props.ref} type="text" className="border-2 rounded-lg p-1" />
    </>
  );
};

export default TodoInput;

typescript:TodoForm.tsx

<form className="flex flex-col">
  <TodoInput labelText="タイトル" ref={titleRef} />
  <TodoInput labelText="内容" ref={contentRef} />
  <button
    type="submit"
    className="bg-black text-white rounded-lg w-full mt-4 p-1"
  >
    登録
  </button>
</form>

新規作成

APIに接続してデータを新規作成します。
ルートディレクトリにutilsディレクトリを作成します。
その中にファイルapi.tsを作成します。
そして関数createTodoを作成します。

export async function createTodo(todo: Todo) {
  const res = await fetch("https://todo-app-d1-base.pages.dev/api/todos", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(todo),
  });
  if (!res.ok) {
    throw new Error("Failed to fetch data");
  }
  return res.json<Todo>();
}

フォームで入力されたデータを取得

"use client";

import { createTodo } from "@/utils/api";
import { useRouter } from "next/navigation";
import { useRef } from "react";

const TodoForm = () => {
  const titleRef = useRef<HTMLInputElement>(null);
  const contentRef = useRef<HTMLInputElement>(null);
  const router = useRouter();
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const title = titleRef.current?.value;
    const content = contentRef.current?.value;
    if (!title || !content)
      return console.log("タイトルと内容を入力してください");
    console.log(`タイトル:${title} 内容:${content}`);
    createTodo({ title, content });
    router.refresh();
  };
  return (
    <form className="flex flex-col" onSubmit={handleSubmit}>
      <label>タイトル</label>
      <input ref={titleRef} type="text" className="border-2 rounded-lg p-1" />
      <label>内容</label>
      <input ref={contentRef} type="text" className="border-2 rounded-lg p-1" />
      <button
        type="submit"
        className="bg-black text-white rounded-lg w-full mt-4 p-1"
      >
        登録
      </button>
    </form>
  );
};

export default TodoForm;

作成したTodoFormコンポーネントをファイルpage.tsxに追加します。

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

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

import TodosTable from "@/components/TodosTable";

ランタイムエッジを追加

プロジェクトをデプロイします。
以下のコマンドを実行します。

npm run pages:deploy

ところが次のようなエラーが発生します。

⚡️ ERROR: Failed to produce a Cloudflare Pages build from the project.
⚡️ 
⚡️      The following routes were not configured to run with the Edge Runtime:
⚡️        - /index
⚡️ 
⚡️      Please make sure that all your non-static routes export the following edge runtime route segment config:
⚡️        export const runtime = 'edge';
⚡️ 
⚡️      You can read more about the Edge Runtime on the Next.js documentation:
⚡️        https://nextjs.org/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes

エラー内容を見ると、/indexexport const runtime = 'edge';が必要なようです。
/indexとはトップページだと認識して、page.tsxexport const runtime = 'edge';を追加します。

export const runtime = "edge";

プロジェクトを再びデプロイします。
無事にデプロイされました。

新規登録後に一覧画面を更新

デプロイしたアプリの動作を確認します。
https://todo-app-d1-base.pages.dev/にアクセスします。
表示されたら、データを新規登録してみます。
ところが、新規登録した後に一覧画面が更新されません。
これはブラウザにキャッシュされていて、データが更新されないためです。
そこで関数createTodocacheオプションにno-storeを追加します。

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

再びデプロイします。
アプリで再び新規登録してみます。
今度は無事に一覧表示が更新されました。