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内に配置された各入力要素は、name属性とvalue属性を持ち、これらの値がクエリパラメータとして生成されます。
入力フォームとその生成されるクエリパラメータ
属性 | 説明 | 値 |
---|---|---|
name |
入力要素の名前 | title |
value |
入力要素の値 | test-title |
ユーザーが入力値をtest-titleという名前で送信すると、次のようなURLが生成されます。
http://example.jp/article?title=test-title
例えば、検索サイトをブラウザで開き、検索キーワードを入力して
そのキーワードで検索すると、URLにクエリパラメータが追加されます。
そのクエリパラメータをサーバー側で解析し、
キーワードで検索を行います。
そして、検索結果をブラウザ上で表示します。
サーバー側での処理
サーバー側では、GETリクエストを受信すると、URLに含まれるクエリパラメータを解析し、対応する処理を実行します。
基本的なHTML <form>
タグの使い方
基本的なHTML
タグの使い方を知らなければ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のインストール
pnpm dlx create-next-app nextjs14
pnpm install zod
基本
export default function Page() {
return (
<form>
<input type="text" name="name" />
<button type="submit">送信</button>
</form>
);
}
Server Actionsの関数定義
'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をフォームタグ
から呼び出す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ページの追加
入力成功後に表示するページ
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)を引数として渡して
その結果でバリデーションが成功したかを判定します。
バリデーションの追加
先程のコードにバリデーションを追加します。
'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 の結果が更新されるたびに、それに応じた処理を自動的に実行することができます。
色々追加して適当に完成した
を利用したソース'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>
);
}
'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');
}
'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を勉強していきます。