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 TField name matching the schema key
labelstringVisible field label
placeholderstringPlaceholder text
descriptionstringHelper text shown below the field
disabledbooleanDisables 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