この記事では、Inertia.jsを使った際のバリデーションエラーの表示について解説します。
前回の記事ではInertia.jsでのフォームとリダイレクトについて解説しました。今回はその続きですのでぜひ前回の記事と合わせてお読みください。
Inertiaでフォームの入力エラーを表示したい場合、どうしたらいいんでしょう?
Inertiaにはバリデーションエラーをキャッチして表示する機能がしっかりついていますので解説しますね!
バリデーションとは?
バリデーションとは、入力された値が正しいか、基準に合っているかを確認することです。さまざ生ルールで値をチェックして、そのチェックに合格しなかった場合は再度入力を促すなどして、必要な値がしっかりと登録され、逆に不正な値は登録されないようにします。
バリデーションの方法
サーバーサイドバリデーション
送られてきた値をサーバー側でチェックすることです。LaravelやRuby on Rails, Djangoなどフレームワークに備わっているバリデーション機能を利用する場合が多いです。
クライアントサイドバリデーション
ブラウザで入力値をチェックすることです。input要素にrequired属性をつけて入力必須であることを知らせたり、email属性を指定してメールアドレスとして妥当かどうかブラウザで送信前にチェックが行えます。
JavaScriptを使ってもっと細かいバリデーションをクライアントサイドで行うこともありますが、バリデーションを通らず直接データが送信される可能性も考慮して、サーバーサイドでのバリデーションと併用して行います。
サンプル
お問い合わせフォームでメールアドレスとお問い合わせ内容を入力するフォームがあり、送信ボタンをクリックすると、エラーがなければ確認ページへ移動し、エラーがある場合は移動せずエラーを表示するというサンプルを作成してみます。
完全なソースコードはGitHubをご覧ください。
web.phpのルーティング
下記の3つのルートを追加します。
/Contactはお問い合わせフォームの表示、/Contact/confirmは確認画面のルート、/Contact/sendは送信完了画面のルートとなります。
use App\Http\Controllers\ContactController;
//略
Route::get('/Contact',[App\Http\Controllers\ContactController::class, 'index'])->name('contact.index');
Route::post('/Contact/confirm',[App\Http\Controllers\ContactController::class, 'confirm'])->name('contact.confirm');
Route::post('/Contact/send',[App\Http\Controllers\ContactController::class, 'send'])->name('contact.send');
Laravelのコントローラー
ContactControllerクラスを作成します。
php artisan make:controller ContactController
コントローラーファイルのひな型として「app/Http/Controllers/ContactController.php」が作成されるのでこのファイルを編集していきます。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Inertia\Inertia;
class ContactController extends Controller
{
//フォーム表示
public function index ()
{
return Inertia::render('Contact/Index');
}
//確認表示
public function confirm (Request $request)
{
$request->validate([
'mail' => ['required', 'email'],
'message' => ['required', 'max:5000'],
]);
return Inertia::render('Contact/Confirm',['mail' => $request->mail, 'message' => $request->message]);
}
//送信
public function send (Request $request)
{
$request->validate([
'mail' => ['required', 'email'],
'message' => ['required', 'max:5000'],
]);
//問い合わせ内容を保存したりメールで送信する処理をここに記述
return Inertia::render('Contact/Complete');
}
}
Laravelでのバリデーション
$request->validate([
'mail' => ['required', 'email'],
'message' => ['required', 'max:5000'],
]);
Laravel側のバリデーションコードは通常と全く同じです。$requestのvalidate()メソッドでリクエストに含まれる値が渡したルールに則っているかをチェックし、エラーがあると元のページを表示するレスポンスが返ります。
そのレスポンスにはエラー内容がInertiaによって自動的に含まれます。Vue側でpropsでそれを用いて画面にエラーを表示することができます。
エラー時のJSONレスポンス例
{
"component": "Contact/Index",
"props": {
"errorBags": {
"default": {
"mail": [
"The mail field is required."
],
"message": [
"The message field is required."
]
}
},
"errors": {
"mail": "The mail field is required.",
"message": "The message field is required."
}
},
"url": "/Contact",
"version": "207fd484b7c2ceeff7800b8c8a11b3b6"
}
Vueコンポーネント
お問い合わせフォームを表示するIndex.vueを作成します。Inertia.jsのフォームヘルパーを使用した例となります。
<script setup>
import { Link, useForm } from '@inertiajs/vue3'
const form = useForm("Contact",{
mail: null,
message: null,
});
const submit = () => {
form.post(route('contact.confirm'));
}
</script>
<template>
<div>
<h2 class="text-lg border-b p-1">
<Link :href="route('contact.index')" class="text-blue-700 underline">
お問い合わせ
</Link>
</h2>
<form @submit.prevent="submit">
<h3 class="border-l-4 p-1">メールアドレスとお問い合わせ内容をご入力ください</h3>
<div v-if="form.hasErrors" class="border border-red-100 p-1 m-1 text-sm text-red-600">
入力された値をもう一度確認してください。
<ul class="list-disc list-inside">
<li v-for="error in form.errors" >{{error}}</li>
</ul>
</div>
<div class="w-40 border-l-4 text-sm p-1 m-1">メールアドレス</div>
<div class="w-80">
<input type=text name=mail v-model="form.mail" class="p-1 m-1 text-sm w-full border" v-bind:class="{'border-red-300': form.errors.mail}"/>
</div>
<div v-show="form.errors.mail" class="p-1 m-1 text-sm text-red-400">{{ form.errors.mail }}</div>
<div class="w-40 border-l-4 text-sm p-1 m-1">お問い合わせ内容</div>
<div class="w-80">
<textarea name=message v-model="form.message" class="p-1 m-1 text-sm w-full h-32 border" v-bind:class="{'border-red-300': form.errors.message}" />
</div>
<div v-show="form.errors.message" class="p-1 m-1 text-sm text-red-400">{{ form.errors.message }}</div>
<button type="submit" class="border bg-green-200 m-1 p-1 text-sm" :disabled="form.processing" v-bind:class="{'cursor-not-allowed': form.processing}">確認画面へ進む</button>
<button @click="form.reset()" type="reset" class="border bg-gray-200 m-1 p-1 text-sm" :disabled="!form.isDirty" v-bind:class="{'cursor-not-allowed': !form.isDirty}">リセット</button>
<button v-if="form.processing" @click="form.cancel()" type="button" class="border bg-red-200 m-1 p-1 text-sm">送信中止</button>
</form>
</div>
</template>
フォームヘルパーの作成
const form = useForm("Contact",{
mail: null,
message: null,
});
formをInertia Formとして定義しています。第一引数でユニークキー(この場合”Contact“)を渡すとフォームに入力した値などがブラウザの履歴に保存されて、戻るボタンなどで戻った時に入力してあったフォームデータが再現されます。
エラーをまとめて表示
<div v-if="form.hasErrors" class="border border-red-100 p-1 m-1 text-sm text-red-600">
入力された値をもう一度確認してください。
<ul class="list-disc list-inside">
<li v-for="error in form.errors" >{{error}}</li>
</ul>
</div>
- div要素を
v-if="form.hasErrors"
でフォームにエラーがある場合にブロックを表示しています。 - li要素を
v-for="error in form.errors"
でエラーの数だけループして各エラーを表示します。
エラーを個別に表示
<div class="w-80">
<input type=text name=mail v-model="form.mail" class="p-1 m-1 text-sm w-full" v-bind:class="{'border-red-300': form.errors.mail}"/>
</div>
<div v-show="form.errors.mail" class="p-1 m-1 text-sm text-red-400">{{ form.errors.mail }}</div>
- input要素に
v-bind:class="{'border-red-300': form.errors.mail}"
で
各フィールドのエラーがある場合は赤色の枠をつけるクラスをバインドしています。 - div要素を
v-show="form.errors.mail"
で、フィールド別にエラーがあったか判定し、エラーがあった場合は{{ form.errors.mail }}
でそのフィールドのエラーを表示しています。
エラーバッグ
route.visit()でデータ送信した際に、複数のフォームが同一ページにある場合、どのフォームのエラーなのかを区別するためにオプションのerrorBag属性にエラーバッグ名を入れておくことができます。
エラーバッグの使い方は以前の記事を参考にしてください。
サンプルソース
ソースコード全体はGitHubに掲載していますのでご覧ください。
お問い合わせ内容の確認と、完了画面のVueコンポーネントを作成します。
入力内容の確認Vueコンポーネント
<script setup>
import { Link, useForm } from '@inertiajs/vue3'
const props = defineProps({
mail: {
type: String
},
message: {
type: String
}
});
const form = useForm("Contact",{
mail: props.mail,
message: props.message,
});
const submit = () => {
form.post(route('contact.send'));
}
const back = () => {
window.history.back();
}
</script>
<template>
<div>
<h2 class="text-lg border-b p-1">
<Link :href="route('contact.index')" class="text-blue-700 underline">
お問い合わせ
</Link>
</h2>
<form @submit.prevent="submit">
<h3 class="border-l-4 p-1">ご入力いただいた内容をご確認ください</h3>
<div class="w-40 border-l-4 text-sm p-1 m-1">メールアドレス</div>
<div class="w-40">
<div class="p-1 m-1 text-sm w-full">
{{ form.mail }}
</div>
<input type=hidden name=mail v-model="form.mail" size=25 />
</div>
<div class="w-40 border-l-4 text-sm p-1 m-1">お問い合わせ内容</div>
<div class="w-40">
<pre class="p-1 m-1 text-sm w-full">{{ form.message }}</pre>
<input type=hidden name=message v-model="form.message" />
</div>
<button type="submit" class="border bg-green-200 m-1 p-1 text-sm" :disabled="form.processing" v-bind:class="{'cursor-not-allowed': form.processing}">お問い合わせを送信</button>
<button @click="back" type="button" class="border bg-gray-200 m-1 p-1 text-sm">入力画面へ戻る</button>
<button v-if="form.processing" @click="form.cancel()" type="button" class="border bg-red-200 m-1 p-1 text-sm">送信中止</button>
</form>
</div>
</template>
完了画面 Vueコンポーネント
<script setup>
import { Link } from '@inertiajs/vue3'
</script>
<template>
<div>
<h2 class="text-lg border-b p-1">
<Link :href="route('contact.index')" class="text-blue-700 underline">
お問い合わせ
</Link>
</h2>
<h3 class="border-l-4 p-1">お問い合わせが送信されました</h3>
</div>
</template>
http://localhost/Contact/へアクセスして動作を確認してみます。
Comments