Laravel Precognition（リアルタイムバリデーション）を試してみた

# Laravel Precognition とは？

バックエンド（Laravel）のバリデーションルールを用いて、フロントエンド（VueやReact）でリアルタイムなバリデーションを実現する機能です。
Laravel 10.x のドキュメントから追加されたようです。

https://laravel.com/docs/10.x/precognition

今回は Laravel + Vue の構成で、以下のログインフォームを想定して動作を検証しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/8c1c705c-dc42-6569-cfc8-840358f0b760.png" width="450" />

# 実装方法

## バックエンド側（Laravel）

まず、バリデーションを行うためのフォームリクエストを作成します。

```php:LoginRequest.php
use Illuminate\Foundation\Http\FormRequest;

class LoginRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'email' => ['required'],
            'password' => ['required', 'string', 'min:8'],
        ];
    }
}
```

次に、作成したフォームリクエストをコントローラーで指定します。
今回は api.php にコントローラーの処理をそのまま記述しています。

```php:api.php
use App\Http\Requests\LoginRequest;

Route::post('/login', function (LoginRequest $request) {
    // コントローラーの処理
});
```

最後に、``` HandlePrecognitiveRequests ``` ミドルウェアを指定します。
バックエンド側はこれだけで準備完了です。

```php:api.php
use App\Http\Requests\LoginRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/login', function (LoginRequest $request) {
    // コントローラーの処理
})->middleware([HandlePrecognitiveRequests::class]);
```

## フロントエンド側（Vue）

まず最初に、laravel-precognition-vue パッケージをインストールします。

```bash
npm install laravel-precognition-vue
```

https://www.npmjs.com/package/laravel-precognition-vue

次に、`useForm()` を使用してフォームオブジェクトを作成します。
パラメータには、HTTPメソッド、URL、初期データを指定します。

```vuejs
<script setup>
import { useForm } from "laravel-precognition-vue";

const form = useForm("post", "/api/login", {
  email: "",
  password: "",
});
</script>
```

そして、リアルタイムバリデーションを有効にしたい入力項目に対して、change イベントで `form.validate()` を指定します。

```vuejs
<input
  v-model="form.email"
  @change="form.validate('email')"
/>
```

ちなみに、ログインボタン押下時に実行する submit イベントには `form.submit()` を指定します。

```vuejs
<script setup>
const submit = () => form.submit();
</script>

<template>
  <form @submit.prevent="submit()">
```

また、エラーメッセージは errors オブジェクトに格納されるので、表示したい場合は以下のように記述します。

```vuejs
<div v-if="form.invalid('email')">
  {{ form.errors.email }}
</div>
```

以上でフロントエンド側も準備完了です。

# 動作検証

# 不正な入力の場合

パスワードの入力欄に不正な入力を行うと、フォーカスが外れると同時にエラーメッセージが表示されました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/82a3ecce-66e0-1e17-7fbd-191f86a5bfb5.png" width="450" />

change イベントで `validate()` が実行されると、`useForm()` で指定した内容をリクエストとして送信するようです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/679bdf72-6128-74b5-a051-b9bfdca26f94.png" width="450" />

返却されたレスポンスを確認してみると、ステータスは「422 Unprocessable Content」でした。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/1d9e242e-a3b0-4d8d-a62c-777377a9bb55.png" width="450" />

# 正しい入力の場合

正しい入力を行うと、エラーメッセージは表示されませんでした。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/9df32b1f-7a05-d770-a707-0c51f4817957.png" width="450" />

返却されたレスポンスを確認してみると、ステータスは「204 No Content」でした。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/41f7b84b-749d-9bdf-7f31-33ef6fe8bbff.png" width="450" />

ちなみに、リアルタイムバリデーションのリクエストでは**正しい入力だったとしてもコントローラーの処理は実行されません。**

# `validate()` と `submit()` は何が違うの？

リアルタイムバリデーションでも、ログインボタン押下時でも、リクエストが送信されるのは同じURLです。どのように判別しているのでしょうか？

 `form.validate()`
→ リアルタイムバリデーション。バリデーションのみでコントローラーの処理は実行しない。
 
`form.submit()`
→ ログインボタン押下時。バリデーションの後、コントローラーの処理を実行する。

そこで `validate()` が実行されたときのリクエストヘッダーを確認したところ、Precognition の項目がありました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/339770/c74e43d0-fd46-f8e8-4f4c-e16f9c128982.png" width="450" />

このヘッダーによって Precognition のリクエスト（リアルタイムバリデーション）かどうかを判別しているようです。