Joey33 wrote a reply+100 XP
4mos ago
I've been trying to implement tanstack from shadcn but had issues with displaying errors returned from failed validation by laravel so ended up with useForm from Inertia and zod for client-side. Works like a charm. Here you are:
import * as z from 'zod';
rules:
...
const LessonSchema = z.object({
title: z
.string()
.min(5, 'Title must be at least 5 characters.')
.max(10, 'Title must be at most 32 characters.')
description: z
.string()
.min(20, 'Description must be at least 20 characters.')
.max(100, 'Description must be at most 100 characters.'),
});
later in the form component closure
// Local state for Zod validation errors (client-side only)
const [clientErrors, setClientErrors] = useState<ClientErrors>({});
// Function to perform client-side validation using Zod
const validateData = (formData: LessonFormData): boolean => {
setClientErrors({});
const result = LessonSchema.safeParse(formData);
if (!result.success) {
const newErrors: ClientErrors = {};
// Map Zod errors to our simple ClientErrors structure
result.error.issues.forEach((issue) => {
const path = issue.path[0] as keyof LessonFormData;
if (path) {
newErrors[path] = issue.message;
}
});
setClientErrors(newErrors);
return false; // Validation failed
}
return true; // Validation passed
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
clearErrors();
// client-side validation
if (!validateData(data)) {
// Validation failed. Stop submission and display clientErrors.
toast.error('Please fix the errors in the form.');
return;
}
post(storeRoute, {
onSuccess: () => {
toast.success('Lesson created successfully!');
// Reset form on successful submission
reset();
},
// Inertia's onError handles Laravel's validation errors (backend)
onError: (serverErrors) => {
console.error('Backend validation failed:', serverErrors);
toast.error('A server validation error occurred.');
},
});
};
// Combine client and server errors for display. Client errors take precedence.
const getError = (fieldName: keyof LessonFormData): string | undefined => {
return clientErrors[fieldName] || errors[fieldName];
};
const isError = (fieldName: keyof LessonFormData): boolean => {
return !!getError(fieldName);
};
form fields
<div>
<label htmlFor="title" className="mb-1 block text-sm font-medium text-gray-700">
Title
</label>
<input
type="text"
id="title"
value={data.title}
// Clear client-side error when user starts typing
onChange={(e) => {
setData('title', e.target.value);
if (clientErrors.title) {
setClientErrors((prev) => ({ ...prev, title: undefined }));
}
}}
className={`w-full rounded-lg border p-3 shadow-sm transition duration-150 ${
isError('title')
? 'border-red-500 focus:ring-red-500'
: 'border-gray-300 focus:ring-green-500'
}`}
onBlur={() => validateData(data)} // Validate on blur for immediate feedback
/>
{getError('title') && <p className="mt-1 text-sm text-red-600">{getError('title')}</p>}
</div>