brodenbrett's avatar

Laravel Precog always returns success

I'm using a fresh install of Laravel, Inertia, and Vue. Here is my set up:

// routes/web.php
use App\Http\Requests\EmailRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
use Illuminate\Support\Facades\Route;

Route::inertia('/', 'Welcome')->name('home');
Route::post('/', function (EmailRequest $request) {
    dd(request()->all(), request()->safe());
})->middleware(HandlePrecognitiveRequests::class);
<?php
// app\Http\Requests\EmailRequest.php
namespace App\Http\Requests;

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;

class EmailRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'email' => 'email',
        ];
    }
}
// resources\js\pages\Welcome.vue
<script setup lang="ts">
import { Form } from '@inertiajs/vue3';
</script>

<template>
    <Form method="post" #default="{errors, validate}">
        <input name="email" value="123">
        <p>{{ errors.email }}</p>
        <a @click="validate()">Validate</a><br>
        <button>Submit</button>
    </Form>
</template>

Clicking Submit will display the error, but clicking Validate will return have precognition return 204 response. Am I missing some code somewhere to make this work?

0 likes
3 replies
LaryAI's avatar
Level 58

Certainly! This issue comes up frequently when working with Laravel Precognition + Inertia with Vue. The main problem is that Laravel's Precognitive validation depends on the Precognition request header and sending form data in the format expected by Laravel, but Inertia's <Form/> component doesn't handle Precognition out of the box.

Key Point

To trigger a Precognitive request, the validate() method must send the request with the proper Precognition header, and form data must be included. Otherwise, the back-end route will always succeed with a 204 No Content because Laravel believes the request is just a "ping" for the route's existence.

Solution

1. Modify Your <input> Tag

Your <input> currently lacks v-model. Thus, its value doesn't get tracked and sent with the validate() call.
Change:

<input name="email" value="123">

To:

<input v-model="form.email" name="email" />

2. Use the Form Helper Correctly

Define your form data:

<script setup>
import { Form } from '@inertiajs/vue3';

const form = Form({
  email: '', // or set a default if you want
});
</script>

3. Use the Correct <Form> Syntax

Put :form="form" on the <Form> tag:

<Form method="post" :form="form" #default="{ errors, validate }">
    <input v-model="form.email" name="email" />
    <p>{{ errors.email }}</p>
    <a @click.prevent="validate()">Validate</a><br>
    <button>Submit</button>
</Form>

Why? The validate() method sends the data in form using a POST request (with the appropriate Precognition header).

4. Make Sure the Middleware is Applied

Your route is correct:

Route::post('/', function (EmailRequest $request) { ... })
    ->middleware(HandlePrecognitiveRequests::class);

This is good!

5. Review the Validation Rule

You have:

'email' => 'email',

For stricter validation, add 'required':

'email' => ['required', 'email'],

6. Final Code Example

routes/web.php

use App\Http\Requests\EmailRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
use Illuminate\Support\Facades\Route;

Route::inertia('/', 'Welcome')->name('home');

Route::post('/', function (EmailRequest $request) {
    // Your logic here
})->middleware(HandlePrecognitiveRequests::class);

app/Http/Requests/EmailRequest.php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    public function rules(): array
    {
        return [
            'email' => ['required', 'email'],
        ];
    }
}

resources/js/Pages/Welcome.vue

<script setup>
import { Form } from '@inertiajs/vue3';

const form = Form({
  email: '',
});
</script>

<template>
    <Form method="post" :form="form" #default="{ errors, validate }">
        <input v-model="form.email" name="email" />
        <p>{{ errors.email }}</p>
        <a @click.prevent="validate()">Validate</a><br>
        <button>Submit</button>
    </Form>
</template>

Now, when you type an invalid email and click "Validate," the validate() method will send a precognitive request with form data and the expected header, and Laravel will respond with validation errors instead of always a 204.


Summary:
The previous code did not actually send the input value to the backend when using validate() (because it wasn't tracked in a reactive form object); thus, Laravel could not validate it, and always returned 204. By using a Form instance and binding the input with v-model, Inertia will send the data, and Laravel will properly return validation errors via Precognition.


Let me know if you have further questions!

imrandevbd's avatar

update your FormRequest to strictly require the field then if you want to manually trigger validation on an untouched input via a button click, you need to pull in the touch method from the slot props and mark the field as touched right before validating.

<template>
    <Form method="post" action="/" #default="{ errors, validate, touch }">
        <input name="email" value="123">
        <p>{{ errors.email }}</p>
        
        <a href="#" @click.prevent="touch('email'); validate('email')">Validate</a><br>
        
        <button type="submit">Submit</button>
    </Form>
</template>
brodenbrett's avatar

Hi, I used validate('email') but this still occurs. The data was being sent to the server in both cases.

validate('email') requires the input to be touched. It results in a POST to /, which resolves to a 302 redirect. This leads to a GET /, which is a 200 and gives me a console error:

Uncaught (in promise) Error: Did not receive a Precognition response. Ensure you have the Precognition middleware in place for the route.

This 302 happens even if I don't have the HandlePrecognitiveRequests middleware in web.php. I'm not sure what could be causing this or if it's intended behaviour.

If I set the HandlePrecognitiveRequests middleware on my GET / route (which I believe is incorrect), then it returns a 204. The appropriate headers are passed between those requests but no data is.

validate() still sends the data to the server. It results in a POST to / which resolves to a 204 even though the data is invalid according to EmailRequest::rules().

Please or to participate in this conversation.