Validation

Learn how to start using Precognition form validation in your Nuxt projects!

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:

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.

type ProfileUpdateForm = {
  email: string
  password: string
}

const payload: ProfileUpdateForm = {
  email: '',
  password: '',
}

const form = usePrecognitionForm<ProfileUpdateForm>('post', '/profile', payload)

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!

Last updated