diff --git a/v2/core-concepts/the-protocol.mdx b/v2/core-concepts/the-protocol.mdx index ac44715..5e7fe06 100644 --- a/v2/core-concepts/the-protocol.mdx +++ b/v2/core-concepts/the-protocol.mdx @@ -143,6 +143,16 @@ The following headers are automatically sent by Inertia when making requests. Yo Indicates whether the requested data should be appended or prepended when using [Infinite scroll](/v2/data-props/infinite-scroll). +The following headers are used for [Precognition](/v2/the-basics/forms#precognition) validation requests. + + + Set to `true` to indicate this is a Precognition validation request. + + + + Comma-separated list of field names to validate. + + ## Response Headers The following headers should be sent by your server-side adapter in Inertia responses. If you're using an official server-side adapter, these are handled automatically. @@ -159,6 +169,20 @@ The following headers should be sent by your server-side adapter in Inertia resp Set to `X-Inertia` to help browsers correctly differentiate between HTML and JSON responses. +The following headers are used for [Precognition](/v2/the-basics/forms#precognition) validation responses. + + + Set to `true` to indicate this is a Precognition validation response. + + + + Set to `true` when validation passes with no errors, combined with a `204 No Content` status code. + + + + Set to `Precognition` on all responses when the Precognition middleware is applied. + + ## The Page Object Inertia shares data between the server and client via a page object. This object includes the necessary information required to render the page component, update the browser's history state, and track the site's asset version. The page object can include the following properties: @@ -442,3 +466,10 @@ Inertia uses specific HTTP status codes to handle different scenarios. | **302 Found** | Standard redirect response. Inertia's server-side adapters automatically convert this to `303 See Other` when returned after `PUT`, `PATCH`, or `DELETE` requests. | | **303 See Other** | Used for redirects after non-GET requests. This status code tells the browser to make a `GET` request to the redirect URL, preventing duplicate form submissions that could occur if the browser repeated the original request method. | | **409 Conflict** | Returned when there's an asset version mismatch or for external redirects. For asset mismatches, this prompts a full page reload. For external redirects, the response includes an `X-Inertia-Location` header and triggers a `window.location` redirect client-side. | + +The following status codes are used for [Precognition](/v2/the-basics/forms#precognition) validation requests. + +| Status Code | Description | +|:------------------------------|:------------------------------------------------------------------------------------------------| +| **204 No Content** | Successful Precognition validation request with no validation errors. | +| **422 Unprocessable Entity** | Precognition validation request with validation errors. The response body contains the errors. | diff --git a/v2/the-basics/forms.mdx b/v2/the-basics/forms.mdx index d906887..621e089 100644 --- a/v2/the-basics/forms.mdx +++ b/v2/the-basics/forms.mdx @@ -954,6 +954,427 @@ function handleSubmit() { In React and Vue, refs provide access to all form methods and reactive state. In Svelte, refs expose only methods, so reactive state like `isDirty` and `errors` should be accessed via [slot props](#slot-props) instead. +### Precognition + +The `
` component includes built-in support for [Laravel Precognition](https://laravel.com/docs/precognition), enabling real-time form validation without duplicating your server-side validation rules on the client. + + +Precognition requires server-side support. Laravel users should see the [Laravel Precognition documentation](https://laravel.com/docs/precognition) for setup instructions. For other frameworks, see the [protocol page](/v2/core-concepts/the-protocol#request-headers) for implementation details. + + +Once your server is configured, call `validate()` with a field name to trigger validation for that field. The `invalid()` helper checks if a field has validation errors, while `validating` indicates when a request is in progress. + + + +```vue Vue icon="vuejs" + +``` + +```jsx React icon="react" + + {({ errors, invalid, validate, validating }) => ( + <> + + validate('name')} /> + {invalid('name') &&

{errors.name}

} + + + validate('email')} /> + {invalid('email') &&

{errors.email}

} + + {validating &&

Validating...

} + + + + )} + +``` + +```svelte Svelte 4 icon="s" +
+ + validate('name')} /> + {#if invalid('name')} +

{errors.name}

+ {/if} + + + validate('email')} /> + {#if invalid('email')} +

{errors.email}

+ {/if} + + {#if validating} +

Validating...

+ {/if} + + +
+``` + +```svelte Svelte 5 icon="s" +
+ {#snippet children({ errors, invalid, validate, validating })} + + validate('name')} /> + {#if invalid('name')} +

{errors.name}

+ {/if} + + + validate('email')} /> + {#if invalid('email')} +

{errors.email}

+ {/if} + + {#if validating} +

Validating...

+ {/if} + + + {/snippet} +
+``` + +
+ +You may also use the `valid()` helper to check if a field has passed validation. + + + +```vue Vue icon="vuejs" +
+ +

Valid email address

+

{{ errors.email }}

+
+``` + +```jsx React icon="react" +
+ {({ errors, invalid, valid, validate }) => ( + <> + validate('email')} /> + {valid('email') &&

Valid email address

} + {invalid('email') &&

{errors.email}

} + + )} +
+``` + +```svelte Svelte 4 icon="s" +
+ validate('email')} /> + {#if valid('email')} +

Valid email address

+ {/if} + {#if invalid('email')} +

{errors.email}

+ {/if} +
+``` + +```svelte Svelte 5 icon="s" +
+ {#snippet children({ errors, invalid, valid, validate })} + validate('email')} /> + {#if valid('email')} +

Valid email address

+ {/if} + {#if invalid('email')} +

{errors.email}

+ {/if} + {/snippet} +
+``` + +
+ + +A form input will only appear as valid or invalid once it has changed and a validation response has been received. + + +#### Validating Multiple Fields + +You may validate multiple fields at once using the `only` option. This is particularly useful when building wizard-style forms where you want to validate all visible fields before proceeding to the next step. + + + +```vue Vue icon="vuejs" +
+ + + + + +
+``` + +```jsx React icon="react" +
+ {({ validate }) => ( + <> + {/* Step 1 fields */} + + + + + + )} +
+``` + +```svelte Svelte 4 icon="s" +
+ + + + + +
+``` + +```svelte Svelte 5 icon="s" +
+ {#snippet children({ validate })} + + + + + + {/snippet} +
+``` + +
+ +#### Touch and Validate + +The `touch()` method marks fields as "touched" without triggering validation. You may then validate all touched fields by calling `validate()` without arguments. + + + +```vue Vue icon="vuejs" +
+ + + + + + +

Name has been touched

+
+``` + +```jsx React icon="react" +
+ {({ validate, touch, touched }) => ( + <> + touch('name')} /> + touch('email')} /> + touch('phone')} /> + + + + {touched('name') &&

Name has been touched

} + + )} +
+``` + +```svelte Svelte 4 icon="s" +
+ touch('name')} /> + touch('email')} /> + touch('phone')} /> + + + + {#if touched('name')} +

Name has been touched

+ {/if} +
+``` + +```svelte Svelte 5 icon="s" +
+ {#snippet children({ validate, touch, touched })} + touch('name')} /> + touch('email')} /> + touch('phone')} /> + + + + {#if touched('name')} +

Name has been touched

+ {/if} + {/snippet} +
+``` + +
+ +The `touched()` helper may also be called without arguments to check if any field has been touched. The `reset()` method clears the touched state for reset fields. + +#### Options + +The `validate()` method accepts an options object with callbacks and configuration. + +```js +validate('username', { + onSuccess: () => { + // Validation passed... + }, + onValidationError: (response) => { + // Validation failed (422 response)... + }, + onBeforeValidation: (newRequest, oldRequest) => { + // Return false to prevent validation... + }, + onFinish: () => { + // Always runs after validation... + }, +}) +``` + +You may also call `validate()` with only an options object to validate specific fields. + +```js +validate({ + only: ['name', 'email'], + onSuccess: () => goToNextStep(), +}) +``` + +Validation requests are automatically debounced. The first request fires immediately, then subsequent changes are debounced (1500ms by default). You may customize this timeout. + + + +```vue Vue icon="vuejs" +
+ +
+``` + +```jsx React icon="react" +
+ {/* ... */} +
+``` + +```svelte Svelte icon="s" +
+ +
+``` + +
+ +By default, files are excluded from validation requests to avoid unnecessary uploads. You may enable file validation when you need to validate file inputs like size or mime type. + + + +```vue Vue icon="vuejs" +
+ +
+``` + +```jsx React icon="react" +
+ {/* ... */} +
+``` + +```svelte Svelte icon="s" +
+ +
+``` + +
+ +By default, validation errors are simplified to strings (the first error message). You may keep errors as arrays to display all error messages for fields with multiple validation rules. + + + +```vue Vue icon="vuejs" +
+ +
+``` + +```jsx React icon="react" +
+ {/* ... */} +
+``` + +```svelte Svelte icon="s" +
+ +
+``` + +
+ ## Form Helper In addition to the `
` component, Inertia also provides a `useForm` helper for when you need programmatic control over your form's data and submission behavior. @@ -1561,6 +1982,311 @@ form.submit(store()) +### Precognition + +Just like the `` component, the `useForm` helper supports [Precognition](#precognition) for real-time validation. You may enable it by chaining the `withPrecognition()` method with the HTTP method and endpoint for validation requests. + + + +```js Vue icon="vuejs" +import { useForm } from '@inertiajs/vue3' + +const form = useForm({ + name: '', + email: '', +}).withPrecognition('post', '/users') +``` + +```js React icon="react" +import { useForm } from '@inertiajs/react' + +const form = useForm({ + name: '', + email: '', +}).withPrecognition('post', '/users') +``` + +```js Svelte icon="s" +import { useForm } from '@inertiajs/svelte' + +const form = useForm({ + name: '', + email: '', +}).withPrecognition('post', '/users') +``` + + + +For backwards compatibility with the `laravel-precognition` packages, you may also pass the method and URL as the first arguments to `useForm()`. + +```js +const form = useForm('post', '/users', { + name: '', + email: '', +}) +``` + +You may also use [Wayfinder](https://github.com/laravel/wayfinder) when enabling Precognition. + +```js +import { store } from 'App/Http/Controllers/UserController' + +const form = useForm({ + name: '', + email: '', +}).withPrecognition(store()) + +// Or passing Wayfinder as the first argument... +const form = useForm(store(), { + name: '', + email: '', +}) +``` + +Once Precognition is enabled, call `validate()` with a field name to trigger validation for that field. The `invalid()` helper checks if a field has validation errors, while `validating` indicates when a request is in progress. + + + +```vue Vue icon="vuejs" + + + +``` + +```jsx React icon="react" +import { useForm } from '@inertiajs/react' + +const { data, setData, post, errors, validating, validate, invalid } = useForm('post', '/users', { + name: '', + email: '', +}) + +function submit(e) { + e.preventDefault() + post('/users') +} + +return ( + + setData('name', e.target.value)} onBlur={() => validate('name')} /> + {invalid('name') &&

{errors.name}

} + + setData('email', e.target.value)} onBlur={() => validate('email')} /> + {invalid('email') &&

{errors.email}

} + + {validating &&

Validating...

} + + + +) +``` + +```svelte Svelte 4 icon="s" + + +
$form.post('/users')}> + $form.validate('name')} /> + {#if $form.invalid('name')} +

{$form.errors.name}

+ {/if} + + $form.validate('email')} /> + {#if $form.invalid('email')} +

{$form.errors.email}

+ {/if} + + {#if $form.validating} +

Validating...

+ {/if} + + +
+``` + +```svelte Svelte 5 icon="s" + + +
{ e.preventDefault(); $form.post('/users') }}> + $form.validate('name')} /> + {#if $form.invalid('name')} +

{$form.errors.name}

+ {/if} + + $form.validate('email')} /> + {#if $form.invalid('email')} +

{$form.errors.email}

+ {/if} + + {#if $form.validating} +

Validating...

+ {/if} + + +
+``` + +
+ +You may also use the `valid()` helper to check if a field has passed validation. + +#### Touch and Validate + +The `touch()` method marks fields as "touched" without triggering validation. You may then validate all touched fields by calling `validate()` without arguments. The `touched()` helper checks if a field has been touched. The `reset()` method clears the touched state for reset fields. + + + +```vue Vue icon="vuejs" + + + + + +

Name has been touched

+``` + +```jsx React icon="react" + setData('name', e.target.value)} onBlur={() => touch('name')} /> + setData('email', e.target.value)} onBlur={() => touch('email')} /> + + + +{touched('name') &&

Name has been touched

} +``` + +```svelte Svelte icon="s" + $form.touch('name')} /> + $form.touch('email')} /> + + + +{#if $form.touched('name')} +

Name has been touched

+{/if} +``` + +
+ +#### Options + +Validation requests are automatically debounced. The first request fires immediately, then subsequent changes are debounced (1500ms by default). You may customize this timeout using `setValidationTimeout()`. + + + +```js Vue icon="vuejs" +const form = useForm('post', '/users', { + name: '', +}).setValidationTimeout(500) +``` + +```js React icon="react" +const form = useForm('post', '/users', { + name: '', +}) + +form.setValidationTimeout(500) +``` + +```js Svelte icon="s" +const form = useForm('post', '/users', { + name: '', +}) + +$form.setValidationTimeout(500) +``` + + + +By default, files are excluded from validation requests to avoid unnecessary uploads. You may enable file validation using `validateFiles()`. + + + +```js Vue icon="vuejs" +const form = useForm('post', '/users', { + avatar: null, +}).validateFiles() +``` + +```js React icon="react" +const form = useForm('post', '/users', { + avatar: null, +}) + +form.validateFiles() +``` + +```js Svelte icon="s" +const form = useForm('post', '/users', { + avatar: null, +}) + +$form.validateFiles() +``` + + + +By default, validation errors are simplified to strings (the first error message). You can indicate you would like all errors as arrays using `withAllErrors()`. + + + +```js Vue icon="vuejs" +const form = useForm('post', '/users', { + name: '', +}).withAllErrors() +``` + +```js React icon="react" +const form = useForm('post', '/users', { + name: '', +}) + +form.withAllErrors() +``` + +```js Svelte icon="s" +const form = useForm('post', '/users', { + name: '', +}) + +$form.withAllErrors() +``` + + + +With Precognition enabled, you may call `submit()` without arguments to submit to the configured endpoint. + ## Server-Side Responses When using Inertia, you don't typically inspect form responses client-side like you would with traditional XHR/fetch requests. Instead, your server-side route or controller issues a [redirect](/v2/the-basics/redirects) response after processing the form, often redirecting to a success page.