Laravel10 + Vue3 + Inertia + PrecognitionでフォームリクエストのバリデーションをVueで使う

# はじめに
この記事では`Precognition`というツールを使って`Inertia + Vue`のバリデーションを実装する方法について書いています。
`Precognition`はLaravelのフォームリクエストで定義したバリデーションを使用して、Vue等のフロントエンドでも使用するためのライブラリです。つまりバリデーションをLaravel側に統一することができます。

https://readouble.com/laravel/10.x/ja/precognition.html

# 前提
* Laravel + Vue + Inertiaで環境構築ができている
* サンプルコードには`<script setup>`構文を使う

# Precognitionを導入・設定
以下の順に作業を行っていきます。
1. フォームリクエストを作成
1. コントローラーを作成
1. Precognitionをインストール
1. ルーティングを設定
1. Vueファイルを作成

`Precognition`の導入方法だけを確認したい方は3.から見ていただければと思います。
それでは説明してきます。

## フォームリクエストを作成
まずはフォームリクエストのファイルを作成します。
```php:app/Http/Requests/UserRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'name' => 'required|max:255',
            'email' => 'required',
        ];
    }
}
```
このようにフォームリクエストは普通にバリデーションを定義するだけで、Vue側にエラーメッセージを表示することができます。

## コントローラーを作成
次にコントローラーを作成します。
```php:app/Http/Controllers/UserController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\UserRequest;
use Inertia\Inertia;

class UserController extends Controller
{
    public function index()
    {
        return Inertia::render('users/index');
    }

    public function store(UserRequest $request)
    {
        return Inertia::render('users/index');
    }
}
```
今回はバリデーションが実装できているかどうかのみを確認するため、`$request`は使いません。

## Precognitionをインストール
ライブラリのインストールを行います。
```sh
$ npm install laravel-precognition-vue-inertia
```
上記は`Inertia + Vue`用のライブラリをインストールするためのコマンドになります。

## ルーティングを設定
ルートに対して`Precognition`を適用する必要があります。
下記のようにルートを定義します。
```php:routes/web.php
<?php

use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

// 略

Route::get('/users', [UserController::class, 'index']);
Route::post('/users', [UserController::class, 'store'])->middleware([HandlePrecognitiveRequests::class]);
```
ミドルウェアとして設定している`HandlePrecognitiveRequests::class`で`Precognition`を適用することができます。

## Vueファイルを作成
最後にVueファイルを作成します。
```vue:resources/js/Pages/users/index.vue
<script setup>
import { useForm } from "laravel-precognition-vue-inertia"

const form = useForm("post", "/users", {
    name: "",
    email: ""
})

const submit = () =>
    form.submit({
        // trueにしておくとリダイレクトされた際にフォームの場所までスクロールしてくれる
        preserveScroll: true, 
        // リクエスト成功時にフォームに入力された内容をリセットする
        onSuccess: () => form.reset()
    })
</script>

<template>
    <form @submit.prevent="submit">
        <div>
            <label for="name">名前</label>
            <input id="name" v-model="form.name" />
            <div v-if="form.invalid('name')" class="error-message">
                {{ form.errors.name }}
            </div>
        </div>
        <div>
            <label for="email">メールアドレス</label>
            <input
                id="email"
                type="email"
                v-model="form.email"
                @change="form.validate('email')"
            />
            <div v-if="form.invalid('email')" class="error-message">
                {{ form.errors.email }}
            </div>
        </div>
        <button :disabled="form.processing">Create User</button>
    </form>
</template>

<style scoped>
form {
    padding: 10px;
}
label {
    display: block;
}
button {
    margin-top: 10px;
}
.error-message {
    color: red;
}
</style>
```
フォームに何も入力せずに作成ボタンを押すと下記のようにエラーメッセージが表示されます。
![Precognition-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1046681/ed544f8a-afd8-7fec-c929-ea6d5ca8fbb4.png)

また、名前フォームに256以上入力して作成ボタンを押すと、文字数超過のメッセージが表示されます。
![Precognition-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1046681/bcfaeea0-4ff3-daef-895d-021850035682.png)

これでフォームリクエストのバリデーションが適用されていることがわかりました。

## 補足
リクエストを送信した際に特定のタイミングで何かしらの処理走らせることができます。
```vue:resources/js/Pages/users/index.vue
<script setup>

// 略
const submit = () =>
    form.submit({
        onBefore: (visit) => console.log("before", visit), // リクエスト前
        onStart: (visit) => console.log("start", visit), // リクエスト開始時
        onError: (errors) => console.log("error", errors), // バリデーションエラー発生時
        onFinish: (visit) => console.log("finish", visit), // リクエスト処理の終了時
        onSuccess: (page) => console.log("success", page), // リクエスト成功時
    })
</script>
// 略
```
`onFinish`はリクエストが成功しても失敗しても実行されます。また、各アロー関数の引数で定義している`visit`、`errors`などはフォームの入力値やエラーメッセージ等が格納されています。気になる方は実際に確認してみてください。

# 参考
https://readouble.com/laravel/10.x/ja/precognition.html

https://inertiajs.com/manual-visits#event-callbacks