Validation
Introduction
Laravel Precognition allows you to anticipate the outcome of a future HTTP request. One of the primary use cases of Precognition is the ability to provide "live" validation for your frontend JavaScript application without having to duplicate your application's backend validation rules.
You can also check the official Laravel documentation for more details about this feature - Laravel Precognition.
Live validation
First, to enable Precognition for a route, the HandlePrecognitiveRequests middleware should be added to the route definition.
You should also create a form request
to house the route's validation rules:
<?php
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (StoreUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);
With nuxt-sanctum-precognition module installed, you can now create a form object using Precognition's
usePrecognitionForm composable, providing the HTTP method (post), the target URL (/users), and the initial form data.
<script lang="ts" setup>
type ProfileUpdateForm = {
email: string
password: string
}
const payload: ProfileUpdateForm = {
email: '',
password: '',
}
const form = usePrecognitionForm<ProfileUpdateForm>('post', '/profile', payload)
</script>
Then, to enable live validation, invoke the form's validate method on each input's change event,
providing the input's name:
<script setup lang="ts">
import type { FetchResponse } from 'ofetch'
type RegistrationForm = {
name: string
email: string
}
const payload: RegistrationForm = {
email: '',
name: '',
}
const form = usePrecognitionForm<RegistrationForm>('post', '/users', payload)
const submit = () => form
.submit()
.then((response: FetchResponse<unknown>) => console.log('Form submitted', response))
.catch((error: FetchResponse<unknown>) => console.error('Form error', error))
</script>
<template>
<form @submit.prevent="submit">
<label for="name">Name</label>
<input
id="name"
v-model="form.fields.name"
@change="form.validate('name')"
>
<div v-if="form.invalid('name')">
{{ form.errors.name }}
</div>
<label for="email">Email</label>
<input
id="email"
v-model="form.fields.email"
type="email"
@change="form.validate('email')"
>
<div v-if="form.invalid('email')">
{{ form.errors.email }}
</div>
<button :disabled="form.processing">
Create User
</button>
</form>
</template>
Now, as the form is filled by the user, Precognition will provide live validation output powered by the validation rules in the route's form request. When the form's inputs are changed, a debounced "precognitive" validation request will be sent to your Laravel application.
You may configure the debounce timeout by changing the validationTimeout config in nuxt.config.ts:
export default defineNuxtConfig({
// ... other config
precognition: {
validationTimeout: 1500,
},
})
When a validation request is in-flight, the form's validating property will be true:
<div v-if="form.validating">
Validating...
</div>
Any validation errors returned during a validation request or a form submission
will automatically populate the form's errors object:
<div v-if="form.invalid('email')">
{{ form.errors.email }}
</div>
You can determine if the form has any errors using the form's hasErrors property:
<div v-if="form.hasErrors">
<!-- ... -->
</div>
You may also determine if an input has passed or failed validation
by passing the input's name to the form's valid and invalid functions, respectively:
<span v-if="form.valid('email')">
✅
</span>
<span v-else-if="form.invalid('email')">
❌
</span>
If you are validating a subset of a form's inputs with Precognition, it can be useful to manually clear errors.
You may use the form's forgetError function to achieve this:
<input
id="avatar"
type="file"
@change="(e) => {
form.avatar = e.target.files[0]
form.forgetError('avatar')
}"
>
As we have seen, you can hook into an input's change event and validate individual inputs as the user interacts with them; however, you may need to validate inputs that the user has not yet interacted with. This is common when building a "wizard", where you want to validate all visible inputs, whether the user has interacted with them or not, before moving to the next step.
To do this with Precognition, you should call the validate method passing the field names you wish to validate
to the only configuration key. You may handle the validation result with onSuccess, onError or onValidationError callbacks:
<button
type="button"
@click="form.validate(
['name', 'email', 'phone'],
{
onSuccess: (response: FetchResponse<unknown>) => /* ... */,
onError: (error) => /* ... */,
onValidationError: (response: FetchResponse<unknown>) => /* ... */,
}
)"
>Next Step</button>
Of course, you may also execute code in reaction to the response to the form submission. The form's submit function returns an ofetch response promise. This provides a convenient way to access the response payload, reset the form inputs on successful submission, or handle a failed request:
const submit = () => form
.submit()
.then((response: FetchResponse<unknown>) => {
form.reset()
alert('User created.')
})
.catch((error: FetchResponse<unknown>) => alert('An error occurred.'))
There is a way also to leverage async-await syntax to submit the form:
async function submit() {
try {
const response = await form.submit()
form.reset()
alert('User created.')
}
catch (e) {
const response = e as FetchResponse<unknown>
alert(`An error occurred - ${response.status}.`)
}
}
You may determine if a form submission request is in-flight by inspecting the form's processing property:
<button :disabled="form.processing">
Submit
</button>
This overview covers most of the use cases. For more details about the composable or error handling, click on the corresponding link!