Livewire Form Validation That Keeps State Without Full Reloads
Use Livewire's validate() and validateOnly() with @error to keep form state across partial updates, show messages in place, and avoid eager validation costs.
22 Mar 2026, 22:30 UTC

The concrete problem is familiar: a multi-field form submits, validation fails, and the user loses what they typed or sees a full page flash. With Livewire, the component stays alive between requests, so the goal is to use that persistence deliberately instead of fighting it.
The useful takeaway is this: define rules on the component, call $this->validate() on submit and $this->validateOnly() on interaction, and render errors with @error so the DOM updates in place while the input values remain in the component properties.
Why validation timing matters in a reactive component
Livewire syncs public component properties with inputs via wire:model. A request cycle runs on every interaction that triggers a server roundtrip. If validation runs eagerly on every keystroke, you pay a request cost for each change and you can show errors before the user is finished.
Two patterns keep UX reasonable:
- Validate on submit with $this->validate(). On failure Livewire returns only the component view, the property values are preserved, and the error bag is populated for that render.
- Validate a single field on blur with validateOnly(). This limits server work to the field that changed and avoids flashing errors while typing.
An error bag is Laravel’s container for validation messages. Livewire stores the bag in the session for the current component request, then makes it available to Blade during the partial render.
How errors stay attached to inputs
Livewire automatically renders the error bag for a property when you use the Blade @error directive. The directive checks the default error bag named "default" and prints the message if present. Because the component is re-rendered, not the whole page, the message appears next to the input without a full reload.
Custom messages are supported per field or globally in the component. You can pass a second argument to validate() with custom texts, or define messages in the component class. If you use multiple validation contexts you must specify the bag explicitly to avoid mixing messages.
Worked example: name field with submit and blur
Create the component from the project root. This requires Laravel CLI access and write permission to app/ and resources/views.
php artisan make:livewire FormExampleExpected check: app/Livewire/FormExample.php and resources/views/livewire/form-example.blade.php are created. Risk: the command overwrites an existing file with the same name.
In app/Livewire/FormExample.php define a public property and rules:
<?php
namespace App\Livewire;
use Livewire\Component;
class FormExample extends Component
{
public string $name = '';
protected function rules()
{
return ['name' => 'required|min:3'];
}
public function submit()
{
$this->validate();
// persist or redirect
}
public function validateField(string $field)
{
$this->validateOnly($field);
}
public function render()
{
return view('livewire.form-example');
}
}In resources/views/livewire/form-example.blade.php bind the input and render errors:
<form wire:submit.prevent="submit">
<label>Name</label>
<input type="text" wire:model="name" wire:blur="validateField('name')" />
@error('name')
<p class="error">{{ $message }}</p>
@enderror
<button type="submit">Save</button>
</form>With this setup, submitting an empty field triggers validate(), the component re-renders, and the error message appears next to the input while the typed value remains in $name. Adding wire:blur calls validateOnly('name'), so validation for that field runs on blur and updates the error state without validating the whole form.
To verify behavior, inspect the generated HTML for the @error('name') block after a failed submit. The error paragraph should be present in the component’s DOM fragment and the input value should be unchanged.
Trade-off: session-backed errors and request cost
Validation errors are stored in the session, so disabling session persistence, for example in stateless API routes, will prevent errors from being shown. The default error bag is "default"; custom templates that do not use @error must manually read $errors.
Livewire validation runs on every request that triggers it. Heavy rules, external lookups, or large forms increase server load. Lazy validation with wire:dirty or blur reduces unnecessary roundtrips, but adds complexity to track which fields have been touched.
Practical check: watch network requests in the browser devtools. A submit should produce one request and a partial HTML diff. A blur should produce a smaller request that only updates the error for that field.
Close with a short checklist: define rules in the component, use validate() for submit, use validateOnly() for per-field checks, render with @error, and keep validation logic light enough to run on each interaction without degrading response time.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.