Validation
Diffyne integrates seamlessly with Laravel's validation system, providing automatic error display and field highlighting.
Basic Validation
Define Rules
class ContactForm extends Component
{
public string $name = '';
public string $email = '';
public string $message = '';
protected function rules(): array
{
return [
'name' => 'required|min:3',
'email' => 'required|email',
'message' => 'required|min:10',
];
}
public function submit()
{
$this->validate(); // Throws ValidationException on failure
// Process form...
}
}Display Errors
<form diff:submit.prevent="submit">
<div>
<input diff:model.defer="name">
<span diff:error="name" class="text-red-500"></span>
</div>
<div>
<input type="email" diff:model.defer="email">
<span diff:error="email" class="text-red-500"></span>
</div>
<div>
<textarea diff:model.defer="message"></textarea>
<span diff:error="message" class="text-red-500"></span>
</div>
<button type="submit">Submit</button>
</form>The diff:error attribute automatically:
- Shows error message when validation fails
- Hides when field becomes valid
- Adds
diffyne-errorclass to the input field
Custom Error Messages
protected function messages(): array
{
return [
'name.required' => 'Please enter your name',
'name.min' => 'Name must be at least 3 characters',
'email.required' => 'Email address is required',
'email.email' => 'Please enter a valid email address',
'message.required' => 'Please enter a message',
'message.min' => 'Message must be at least 10 characters',
];
}Custom Attribute Names
protected function validationAttributes(): array
{
return [
'email' => 'email address',
'message' => 'message content',
];
}Error messages will use these names: "The email address must be valid" instead of "The email must be valid".
Validation Methods
validate()
Validates all properties defined in rules():
public function submit()
{
$validated = $this->validate();
// $validated contains only validated data
// Use validated data
Mail::send($validated);
}validateOnly()
Validates a single field:
public function updated($field)
{
if ($field === 'email') {
$this->validateOnly('email');
}
}resetValidation()
Clears all validation errors:
public function clearForm()
{
$this->reset();
$this->resetValidation();
}Clear specific field errors:
$this->resetValidation('email');
$this->resetValidation(['email', 'name']);addError()
Manually add validation errors:
public function checkAvailability()
{
if (User::where('email', $this->email)->exists()) {
$this->addError('email', 'This email is already taken');
}
}Real-time Validation
Validate fields as user types or on blur:
<input
diff:model.lazy="email"
diff:change="validateEmail">
<span diff:error="email"></span>Component:
public function validateEmail()
{
$this->validateOnly('email');
}
protected function rules(): array
{
return [
'email' => 'required|email|unique:users',
];
}Complex Validation Rules
Conditional Rules
protected function rules(): array
{
return [
'email' => 'required|email',
'phone' => $this->contactMethod === 'phone' ? 'required' : '',
'company' => $this->type === 'business' ? 'required' : '',
];
}Dynamic Rules
protected function rules(): array
{
$rules = [
'name' => 'required|min:3',
'email' => 'required|email',
];
if ($this->updatePassword) {
$rules['password'] = 'required|min:8|confirmed';
}
return $rules;
}Array Validation
public array $emails = [''];
protected function rules(): array
{
return [
'emails' => 'required|array|min:1',
'emails.*' => 'required|email',
];
}Nested Object Validation
public array $address = [
'street' => '',
'city' => '',
'zip' => '',
];
protected function rules(): array
{
return [
'address.street' => 'required',
'address.city' => 'required',
'address.zip' => 'required|numeric',
];
}Validation with Database Checks
Unique Validation
protected function rules(): array
{
return [
'email' => 'required|email|unique:users,email',
'username' => 'required|unique:users,username',
];
}Ignore current user when updating:
protected function rules(): array
{
return [
'email' => ['required', 'email', Rule::unique('users')->ignore($this->userId)],
];
}Exists Validation
protected function rules(): array
{
return [
'category_id' => 'required|exists:categories,id',
'user_id' => 'required|exists:users,id',
];
}Custom Validation Rules
Inline Closure
use Illuminate\Validation\Rule;
protected function rules(): array
{
return [
'coupon' => [
'required',
function ($attribute, $value, $fail) {
if (!$this->isValidCoupon($value)) {
$fail('The coupon code is invalid or expired.');
}
},
],
];
}
private function isValidCoupon($code): bool
{
return Coupon::where('code', $code)
->where('expires_at', '>', now())
->exists();
}Custom Rule Class
use App\Rules\ValidCoupon;
protected function rules(): array
{
return [
'coupon' => ['required', new ValidCoupon()],
];
}Error Styling
Automatic Field Highlighting
When validation fails, Diffyne automatically adds diffyne-error class to input fields. Style them in your CSS:
.diffyne-error {
border-color: #ef4444;
outline: none;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}Manual Field Styling
Use Blade directives for conditional classes:
<input
diff:model.defer="email"
class="border rounded px-3 py-2
@error('email') border-red-500 @enderror">Error Message Container
<div>
<input diff:model.defer="email">
{{-- Diffyne automatic error --}}
<span diff:error="email" class="text-red-500 text-sm"></span>
{{-- Or manual Blade error --}}
@error('email')
<span class="text-red-500 text-sm">{{ $message }}</span>
@enderror
</div>Complete Example
<form diff:submit.prevent="register">
{{-- Name Field --}}
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Name</label>
<input
type="text"
diff:model.defer="name"
class="w-full border rounded px-3 py-2 focus:outline-none focus:ring-2">
<span diff:error="name" class="text-red-500 text-sm mt-1 block"></span>
</div>
{{-- Email Field --}}
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Email</label>
<input
type="email"
diff:model.defer="email"
class="w-full border rounded px-3 py-2 focus:outline-none focus:ring-2">
<span diff:error="email" class="text-red-500 text-sm mt-1 block"></span>
</div>
{{-- Password Field --}}
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Password</label>
<input
type="password"
diff:model.defer="password"
class="w-full border rounded px-3 py-2 focus:outline-none focus:ring-2">
<span diff:error="password" class="text-red-500 text-sm mt-1 block"></span>
</div>
{{-- Password Confirmation --}}
<div class="mb-4">
<label class="block text-sm font-medium mb-2">Confirm Password</label>
<input
type="password"
diff:model.defer="password_confirmation"
class="w-full border rounded px-3 py-2 focus:outline-none focus:ring-2">
<span diff:error="password_confirmation" class="text-red-500 text-sm mt-1 block"></span>
</div>
{{-- Submit Button --}}
<button
type="submit"
diff:loading.class="opacity-50 cursor-not-allowed"
diff:loading.attr="disabled"
class="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600">
<span diff:loading.remove>Register</span>
<span diff:loading>Registering...</span>
</button>
</form>
@if($registered)
<div class="mt-4 p-4 bg-green-100 text-green-700 rounded">
Registration successful!
</div>
@endifComponent:
<?php
namespace App\Diffyne;
use App\Models\User;
use Diffyne\Component;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
class RegisterForm extends Component
{
public string $name = '';
public string $email = '';
public string $password = '';
public string $password_confirmation = '';
public bool $registered = false;
protected function rules(): array
{
return [
'name' => ['required', 'string', 'min:3', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'confirmed', Password::min(8)],
];
}
protected function messages(): array
{
return [
'name.required' => 'Please enter your full name',
'name.min' => 'Name must be at least 3 characters',
'email.required' => 'Email address is required',
'email.email' => 'Please enter a valid email address',
'email.unique' => 'This email is already registered',
'password.required' => 'Password is required',
'password.confirmed' => 'Passwords do not match',
];
}
public function register()
{
$validated = $this->validate();
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
auth()->login($user);
$this->registered = true;
// Optionally redirect
// return redirect('/dashboard');
}
}Best Practices
1. Always Validate on Server
Never trust client-side validation alone:
public function submit()
{
// Server-side validation is mandatory
$this->validate();
// Process data...
}2. Use Type-safe Properties
// Good
public string $email = '';
public int $age = 0;
public bool $active = false;
// Avoid
public $email;
public $age;3. Provide Clear Error Messages
protected function messages(): array
{
return [
'email.email' => 'Please enter a valid email address',
'password.min' => 'Password must be at least 8 characters long',
];
}4. Use diff:error for Automatic Display
{{-- Preferred - automatic error display --}}
<span diff:error="email"></span>
{{-- Also works but less reactive --}}
@error('email')
<span>{{ $message }}</span>
@enderror5. Reset Form After Success
public function submit()
{
$this->validate();
// Process form...
// Clear form
$this->reset();
$this->resetValidation();
}Next Steps
Keep learning about forms and validation:
- Forms - Form handling and submission
- Data Binding - Two-way data synchronization
- Error Handling - Handle errors gracefully
- Contact Form Example - Complete working example
