Skip to main content

Forms & Validation System

BonardaHR standardizes all form inputs using React Hook Form and Zod schemas via @hookform/resolvers/zod.


Form Architecture​

[ Zod Schema (z.object({ ... })) ]
│
â–¼
[ useForm({ resolver: zodResolver(schema) }) ]
│
â–¼
[ FormFields (TextInput, SelectField, DatePickerField, CheckboxField) ]
│
â–¼
[ Native Input / Custom Selector binds with register or Controller ]
│
â–¼
[ Display Inline Errors from formState.errors ]

Step-by-Step Implementation Example​

1. Define Zod Schema & TypeScript Type​

import { z } from 'zod';

export const createDepartmentSchema = z.object({
name: z.string().min(2, 'Department name must be at least 2 characters'),
code: z.string().min(2, 'Department code is required').toUpperCase(),
managerId: z.string().optional(),
description: z.string().max(500, 'Description is too long').optional(),
});

export type CreateDepartmentFormData = z.infer<typeof createDepartmentSchema>;

2. Connect in Form Component​

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { createDepartmentSchema, CreateDepartmentFormData } from './department.schema';
import Button from '@/shared/components/ui/Button';

export function CreateDepartmentForm({ onSubmit }: { onSubmit: (data: CreateDepartmentFormData) => void }) {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<CreateDepartmentFormData>({
resolver: zodResolver(createDepartmentSchema),
defaultValues: {
name: '',
code: '',
},
});

return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">Department Name</label>
<input
{...register('name')}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
/>
{errors.name && <p className="mt-1 text-xs text-red-500">{errors.name.message}</p>}
</div>

<div>
<label className="block text-sm font-medium text-gray-700">Department Code</label>
<input
{...register('code')}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none"
/>
{errors.code && <p className="mt-1 text-xs text-red-500">{errors.code.message}</p>}
</div>

<Button type="submit" variant="primary" isLoading={isSubmitting}>
Save Department
</Button>
</form>
);
}