Skip to Content
Welcome to the official DBS Documentation! 📚

Forms

DBS uses React Hook Form + Zod for all form validation, with a set of pre-built shadcn/ui wrapper components that handle labels, error messages, and field registration automatically.

Form Architecture

Every form in DBS follows this pattern:

  1. Zod schema — defines the shape and validation rules
  2. useForm with the Zod resolver — creates the form instance
  3. Form provider wrapper — connects React Hook Form to the component tree
  4. FormInput / FormSelect / FormDatePicker — drop-in form field components
  5. onSubmit handler — calls the service layer and handles toast feedback

Zod Schema

Define your schema separately — this also gives you a typed infer interface:

import { z } from 'zod'; const createUserSchema = z.object({ name: z.string().min(2, 'Name must be at least 2 characters'), email: z.string().email('Invalid email address'), role: z.string().min(1, 'Role is required'), joinedAt: z.date({ required_error: 'Join date is required' }), }); type CreateUserValues = z.infer<typeof createUserSchema>;

Basic Form Example

'use client'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Form } from '@/components/ui/form'; import { FormInput } from '@/components/common/form/FormInput'; import { FormSelect } from '@/components/common/form/FormSelect'; import { FormDatePicker } from '@/components/common/form/FormDatePicker'; import { Button } from '@/components/ui/button'; import { usersApi } from '@/services/users/api'; import { toast } from 'sonner'; export function CreateUserForm() { const form = useForm<CreateUserValues>({ resolver: zodResolver(createUserSchema), defaultValues: { name: '', email: '', role: '', }, }); const onSubmit = async (values: CreateUserValues) => { try { await usersApi.createUser(values); toast.success('User created successfully'); form.reset(); } catch (error) { toast.error('Failed to create user'); } }; return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> <FormInput control={form.control} name="name" label="Full Name" placeholder="Jane Doe" /> <FormInput control={form.control} name="email" label="Email Address" type="email" placeholder="jane@example.com" /> <FormSelect control={form.control} name="role" label="Role" options={[ { value: 'admin', label: 'Administrator' }, { value: 'manager', label: 'Manager' }, { value: 'user', label: 'User' }, ]} /> <FormDatePicker control={form.control} name="joinedAt" label="Start Date" /> <Button type="submit" disabled={form.formState.isSubmitting}> {form.formState.isSubmitting ? 'Creating...' : 'Create User'} </Button> </form> </Form> ); }

Form Field Components

FormInput

import { FormInput } from '@/components/common/form/FormInput'; <FormInput control={form.control} name="password" label="Password" type="password" // text | email | password | number placeholder="••••••••" description="At least 8 characters" // Optional hint text />

FormSelect

import { FormSelect } from '@/components/common/form/FormSelect'; <FormSelect control={form.control} name="status" label="Status" placeholder="Select status..." options={[ { value: 'active', label: 'Active' }, { value: 'inactive', label: 'Inactive' }, { value: 'pending', label: 'Pending' }, ]} />

FormDatePicker

import { FormDatePicker } from '@/components/common/form/FormDatePicker'; <FormDatePicker control={form.control} name="dueDate" label="Due Date" disabledDates={(date) => date < new Date()} // Optional: disable past dates />

Form Component Props

All three components share these base props:

PropTypeRequiredDescription
controlControl<T>✅React Hook Form control object
namekeyof T✅Field name matching the schema key
labelstring✅Visible field label
placeholderstring—Placeholder text
descriptionstring—Helper text shown below the field
disabledboolean—Disables the input

Error messages are displayed automatically from Zod validation — no manual error rendering needed.

Handling Loading & Submit States

<Button type="submit" disabled={form.formState.isSubmitting}> {form.formState.isSubmitting ? ( <> <Loader2 className="mr-2 h-4 w-4 animate-spin" /> Saving... </> ) : ( 'Save Changes' )} </Button>

form.formState.isSubmitting is automatically true while the onSubmit handler is executing an async operation. You don’t need to manage a separate loading state.

Edit Forms (Pre-filled Values)

Pass defaultValues to pre-fill a form for editing:

const form = useForm<CreateUserValues>({ resolver: zodResolver(createUserSchema), defaultValues: { name: existingUser.name, email: existingUser.email, role: existingUser.role, }, });

If the data for defaultValues is loaded asynchronously (e.g., from useData), call form.reset(data) inside a useEffect when the data becomes available instead of passing it to defaultValues at initialization.

Last updated on