HTML <form>タグの基礎の勉強 (Zod Next.js conform)

Next.jsの ServerActionはconformを使うと良いそうなので勉強します。
conform を使えるようになるために まずは、HTML `<form>`タグを一から勉強します。

# HTML formタグの基礎

```
<form>
</form>

```

HTMLの基本タグである`<form>`は👆の様に囲って使います。
この中に複数の項目をまとめて、ブラウザからサーバーに送信します。

ここには、👇以下の属性を設定できます。

id：フォームに一意のIDを指定します。
action: 送信先となるサーバー側のプログラムのURLを指定します。省略すると、現在のページに送信されます。

method: 送信方法を指定します。GET または POST のいずれかを指定できます。
※Next.js Reactでは サーバー関数を実行できるように拡張されています。

name: フォームの名前を指定します。

target：送信先のフレームまたはウィンドウを指定します。

enctype: ファイルアップロードを行う場合に、エンコーディング方式を指定します。

accept-charset：送信データで許容される文字エンコーディングを指定します。



## 入力項目を作成する

主なHTMLタグ

`<input>`: テキスト入力、パスワード入力、チェックボックス、ラジオボタンなど、様々な入力項目を作成できます。
`<select>`: ドロップダウンリストを作成できます。
`<textarea>`: 複数行のテキスト入力エリアを作成できます。

Confromでは全てのHTMLタグを使えます。

HTMLタグには属性を追加します。

主な属性

id: 入力項目にIDを指定します。
type: 入力項目の種類を指定します。
name: 入力項目の名前を指定します。送信時にこの名前で値が渡されます。
value: 入力項目の初期値を指定します。



## 送信ボタン

これら複数の項目をまとめてサーバーに送ります。

HTMLのfor属性は、`<label>`要素がどのフォーム要素に関連付けられているかを指定します。
for属性の値は、関連付けるフォーム要素のid属性の値と一致する必要があります。

```
<label for="name">名前:</label>
<input type="text" id="name" name="name" />

```

※htmlFor属性は、HTMLのfor属性に対応するもので、ReactではhtmlForという名前で使用します。これはJavaScriptの予約語forとの衝突を避けるためです。

```
<button type="submit">送信</button>

```

formの項目を動的に追加、削除、変更する方法はJavaScriptを使用します。



## クエリパラメータ

<form method="GET" action="http://example.jp/article">
    <label>題名 <input type="text" id="title" name="title"></label>
    <input type="submit" name="submit" value="検索">
</form>


form内に配置された各入力要素は、name属性とvalue属性を持ち、これらの値がクエリパラメータとして生成されます。

## 入力フォームとその生成されるクエリパラメータ

| 属性 | 説明 | 値 |
|---|---|---|
| `name` | 入力要素の名前 | `title` |
| `value` | 入力要素の値 | `test-title` |

ユーザーが入力値をtest-titleという名前で送信すると、次のようなURLが生成されます。

`http://example.jp/article?title=test-title`


例えば、検索サイトをブラウザで開き、検索キーワードを入力して
そのキーワードで検索すると、URLにクエリパラメータが追加されます。
そのクエリパラメータをサーバー側で解析し、
キーワードで検索を行います。
そして、検索結果をブラウザ上で表示します。



### サーバー側での処理

サーバー側では、GETリクエストを受信すると、URLに含まれるクエリパラメータを解析し、対応する処理を実行します。





----------------------------------------

# 基本的なHTML `<form>`タグの使い方

基本的なHTML <form>タグの使い方を知らなければ
conformで何が便利になったか、どこが拡張されたのかわかりません。

基本的なHTMLタグを使った使用例

例1

```
export default function BasicForm() {
  return (
    <form action="/" method="post">
      <label htmlFor="name">名前:</label>
      <input type="text" id="name" name="name" />
      <br />
      <label htmlFor="email">メールアドレス:</label>
      <input type="email" id="email" name="email" />
      <br />
      <label htmlFor="message">メッセージ:</label>
      <textarea id="message" name="message" />
      <br />
      <button type="submit">送信</button>
    </form>
  );
}

```

例2

```
<form method="GET" action="http://example.jp/article">
    <label>題名 <input type="text" id="title" name="title"></label>
    <input type="submit" name="submit" value="検索">
</form>

```



----------------------------------------
----------------------------------------
----------------------------------------

# 参考

Next.jsで素朴なフォームをシンプルに作る
https://zenn.dev/moozaru/articles/58bd654bbc402c

参考サイトのコア部分を自分なりに勉強します。



----------------------------------------

# Next.js Zodのインストール

```terminal
pnpm dlx create-next-app nextjs14

pnpm install zod

```



## 基本

```src/app/simple/page.tsx
export default function Page() {
  return (
    <form>
      <input type="text" name="name" />
      <button type="submit">送信</button>
    </form>
  );
}

```



## Server Actionsの関数定義

```src/app/simple/postAction.ts
'use server';

import { redirect } from 'next/navigation';

export async function postAction(formData: FormData) {
  const name = formData.get('name');
  console.log(name);

  // 入力されたデータが取得できたので
  // 何らかのデータ処理をします。

  // その後、リダイレクトします。
  redirect('/simple/thanks');
}

```



## Server Actionsの使用条件

"use server"; でバックエンド側であることを宣言する。
非同期処理である async にする。
引数に FormData オブジェクトを渡す



## Server Actionsをフォームタグ<form>から呼び出す

```src/app/simple/page.tsx
import { postAction } from "@/app/simple/postAction";

export default function Page() {
  return (
    <form action={postAction}>
      <input type="text" name="name" />
      <button type="submit">送信</button>
    </form>
  );
}

```

これで基本的なServerActionの動作確認が出来ました。



----------------------------------------

# thanksページの追加

入力成功後に表示するページ

```src/app/simple/thanks/page.tsx
import Link from 'next/link';

export default function Page() {
  return (
    <div>
      投稿ありがとうございます。
      <br />
      <Link href="/simple">フォームに戻る</Link>
    </div>
  );
}

```



----------------------------------------

# Zodってなに？

Zodは、JavaScript及びTypeScript向けに開発された強力なスキーマベースのバリデーションライブラリです。データの構造を定義し、そのデータが期待通りの形式かどうかを検証する機能を提供します。

従来のバリデーションライブラリと異なり、Zodは以下のような利点を持ちます。

* **直感的なスキーマ定義**: スキーマはシンプルなオブジェクト形式で記述でき、コードの可読性とメンテナンス性を向上させます。
* **強力な型**: TypeScriptとの緊密な統合により、型安全性を高め、実行時エラーを抑制します。
* **柔軟なバリデーションルール**: 型チェックだけでなく、長さ、範囲、フォーマットなどの様々なバリデーションルールを定義できます。
* **再利用可能なスキーマ**: 定義したスキーマを様々な箇所で再利用することで、コードの重複を削減できます。
* **詳細なエラーメッセージ**: バリデーションエラー発生時に、詳細なエラーメッセージを出力し、問題箇所を特定しやすくなります。

Zodは、クライアントサイドとサーバーサイドの両方で使用でき、フォームデータのバリデーション、APIリクエストの検証、データ変換など、様々なユースケースで活用できます。



## バリデーション

フォームに入力されたデータが正しいかどうかをチェックすることです。



## Zodを使ったバリデーション

通常バリデーションは
クライアント
サーバー
両方で行うのが安心です。

クライアントで大まかにチェックして
サーバー側で詳細なチェックを行うのが理想です。

バリデーションにzodを使用します。



## スキーマ

要するにバリデーションのルールを決めるってことです。
これをスキーマと呼びます。

👇スキーマの例

```
const UserSchema = z.object({
  username: z.string().min(1, 'ユーザー名必須').max(20, '20文字以内')
});

```

このようにスキーマはそれぞれの項目の型定義＋ルールみたいなもので、
後から追加や削除が簡単に出来ます。



## スキーマを使ったバリデーション

受け取ったformDataにバリデーション関数？を使っています。

※バリデーション関数という使い方は初耳
スキーマ関数と呼んだほうが直感的か？

バリデーションを関数形式にして
引数にformDataを渡すという感じで使います。

## 基本的な形

```
UserSchema(formData)

```

👆Zodを使ってバリデーション関数を作り
フォームに入力されたデータ(＝formData)を引数として渡して
その結果でバリデーションが成功したかを判定します。





----------------------------------------

# バリデーションの追加

先程のコードにバリデーションを追加します。



```app\simple\postAction.ts
'use server';

import console from 'console';

import { redirect } from 'next/navigation';
import { z } from 'zod';

const UserSchema = z.object({
  username: z.string().min(1, 'ユーザー名必須').max(20, '20文字以内')
});

export async function postAction(formData: FormData) {
  const result = UserSchema.safeParse({
    username: formData.get('name') as string
  });
  // バリデーション判定
  if (!result.success) {
    console.log('バリデーションエラーがあります:', result.error);
  } else {
    console.log('バリデーション成功');
  }

  // ユーザーネームを取り出す1。バリデーション前のコード
  // 受け取ったformDataからnameを取り出します。
  console.log('==formData get name', formData.get('name'));

  const name = formData.get('name');

  // ユーザーネームを取り出す2。バリデーション後のコード
  // safeParse()でバリデーションを行い、成功した場合はresult.dataにデータが入ります。
  console.log('==result.data.username', result.data?.username);

  // 入力されたデータが取得できたので
  // 何らかのデータ処理をします。

  // その後、リダイレクトします。
  redirect('/simple/thanks');
}

```

これで基本的なバリデーションが出来ました。



----------------------------------------

# スキーマから型を生成

## スキーマから型を取得する

```
type InputsType = z.infer<typeof UserSchema>;
const inputs: InputsType = result.data as InputsType;
console.log('==inputs.username', inputs.username);

```

inferはスキーマから型を取得します。

👆のコードは、UserSchemaから型を取得し、inputsに代入しています

。

----------------------------------------

# useFormState

useFormStateは、Server Actionsの返り値をリアクティブに扱えるようにしてくれます

## リアクティブ

データの変化に自動的に反応し、それに応じて処理を実行します。

つまり、useFormState を使うと、Server Actions の結果が更新されるたびに、それに応じた処理を自動的に実行することができます。



----------------------------------------

# 色々追加して適当に完成した<form>を利用したソース

```app\simple\page.tsx
'use client';

import { useFormState, useFormStatus } from 'react-dom';

import { postAction } from './postAction';

function Submit() {
  const status = useFormStatus();
  return (
    <button type="submit" disabled={status.pending}>
      {status.pending ? '送信中...' : '送信'}
    </button>
  );
}

export default function Page() {
  const [result, dispatch] = useFormState(
    (state: any, payload: FormData) => postAction(state, payload),
    undefined
  );

  return (
    <form action={dispatch}>
      {result && result.errors && <div>{result.errors.name}</div>}
      <input type="text" name="name" />
      <Submit />
    </form>
  );
}

```



```app\simple\postAction.ts
'use server';

import console from 'console';

import { redirect } from 'next/navigation';
import { z } from 'zod';

interface ReturnType {
  errors?: {
    name?: string;
  };
}

const UserSchema = z.object({
  username: z.string().min(1, 'ユーザー名必須').max(20, '20文字以内')
});

export async function postAction(
  prevData: ReturnType,
  formData: FormData
): Promise<ReturnType> {
  // バリデーション
  const result = UserSchema.safeParse({
    username: formData.get('name') as string
  });
  // バリデーション判定
  if (!result.success) {
    console.log('バリデーションエラーがあります:', result.error);
  } else {
    console.log('バリデーション成功');
  }

  // ユーザーネームを取り出す1。バリデーション前のコード
  // 受け取ったformDataからnameを取り出します。
  console.log('==formData get name', formData.get('name'));

  const name = formData.get('name');

  // ユーザーネームが空でないことを確認
  if (!name) {
    // ユーザーネームが空の場合はもう一度入力を促す
    redirect('/simple/');
  }
  // ユーザーネームを取り出す2。バリデーション後のコード
  // safeParse()でバリデーションを行い、成功した場合はresult.dataにデータが入ります。
  console.log('==result.data.username', result.data?.username);

  // スキーマから型を取得する
  type InputsType = z.infer<typeof UserSchema>;
  const inputs: InputsType = result.data as InputsType;
  console.log('==inputs.username', inputs.username);

  // 入力されたデータが取得できたので
  // 何らかのデータ処理をします。

  // その後、リダイレクトします。
  redirect('/simple/thanks');
}

```



```app\simple\thanks\page.tsx
'use client';

import Link from 'next/link';

export default function Page() {
  return (
    <div>
      投稿ありがとうございます。
      <br />
      <Link href="/simple">フォームに戻る</Link>
    </div>
  );
}

```



これで基本的なHTML `<from>`タグの勉強は終了です。
この基礎的な`<form>`タグを元に次はconformを勉強していきます。

※メッセージ等、所々足りてません。

※useFormStateとuseFormStatusがなくなる？
useActionStateという新しい名前に変わるそうです。


この基礎的な `<form>`タグ の知識を身に着けた上で、comformを勉強していきます。