# Form Validation Typed form primitives and three pre-built forms for Next.js 14+ — built on React Hook Form + Zod, with server-side validation and a multi-step hook included. ## What's included **Field components** - `Field` — input for text, email, password, number, tel; shows inline error and optional hint - `TextareaField` — resizable textarea with optional character counter - `SelectField` — styled native select with chevron, placeholder support, inline error - `CheckboxField` — checkbox with ReactNode label (supports JSX like links inside label) **Pre-built forms** - `RegisterForm` — name + email + password + GitHub username; server error display built in - `ContactForm` — name/email grid + subject + message with char count; success state built in - `ChangePasswordForm` — current + new + confirm password; calls `onSuccess` callback on completion **Schemas (Zod)** - `Schemas` — individual reusable primitives: `email`, `password`, `name`, `url`, `phone`, `githubUsername`, `positiveInt`, `uuid` - `LoginSchema`, `RegisterSchema`, `ContactSchema`, `ChangePasswordSchema`, `ProfileSchema` — composed schemas ready to use - Matching TypeScript types: `LoginInput`, `RegisterInput`, `ContactInput`, `ChangePasswordInput`, `ProfileInput` **Utilities** - `validateBody(schema, body)` — server-side Zod parse; throws a readable error string on failure, returns typed data on success - `useMultiStep(totalSteps)` — returns `step`, `next`, `prev`, `goTo`, `isFirst`, `isLast`, `progress` ## Setup ### 1. Install dependencies ```bash npm install react-hook-form @hookform/resolvers zod ``` ### 2. Add to your project ```tsx // Any client component — mark with 'use client' import { Field, LoginSchema, type LoginInput } from '@/blocks/formvalidation' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' const form = useForm({ resolver: zodResolver(LoginSchema) }) ``` ## Usage examples ```tsx // Custom form using primitives 'use client' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { Field, TextareaField, ProfileSchema, type ProfileInput } from '@/blocks/formvalidation' export function ProfileForm({ onSubmit }: { onSubmit: (d: ProfileInput) => Promise }) { const form = useForm({ resolver: zodResolver(ProfileSchema) }) return (
) } ``` ```tsx // Drop-in pre-built forms import { RegisterForm, ContactForm } from '@/blocks/formvalidation' { await fetch('/api/auth/register', { method: 'POST', body: JSON.stringify(data) }) }} /> { await fetch('/api/contact', { method: 'POST', body: JSON.stringify(data) }) }} /> ``` ```ts // Server-side validation in an API route import { validateBody, RegisterSchema } from '@/blocks/formvalidation' export async function POST(req: Request) { const body = await req.json() const data = validateBody(RegisterSchema, body) // throws on invalid input // data is fully typed as RegisterInput } ``` ## Notes - All field components are `'use client'` — the file has the directive at the top, so don't import field components from server components directly; re-export them from a client boundary if needed - Styling uses inline styles with CSS variables (`--text`, `--border`, `--bg`, `--accent`) — override these on `:root` to match your theme; no Tailwind dependency - `validateBody` throws a plain `Error` with a semicolon-joined message like `email: Invalid email; password: Min 8 chars` — catch it in your API route and return a 400 - `useMultiStep` manages step state only — it doesn't validate each step before advancing; call `form.trigger(fieldsOnThisStep)` before calling `ms.next()` if you want per-step validation