import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
const Schema = z.object({ title: z.string().min(2).max(120) });
type Form = z.infer<typeof Schema>;
export function CreateSnipForm() {
const { register, handleSubmit, formState } = useForm<Form>({ resolver: zodResolver(Schema) });
const onSubmit = handleSubmit(async (data) => {
await fetch('/api/snips', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(data) });
});
return (
<form onSubmit={onSubmit}>
<input {...register('title')} />
{formState.errors.title?.message && <p>{formState.errors.title.message}</p>}
<button type="submit">Create</button>
</form>
);
}
Forms are where type safety and UX collide. react-hook-form keeps re-renders low, and Zod gives me a single schema I can share between frontend and backend if I want. I wire zodResolver so field-level errors show up automatically, and I keep a stable submit handler that maps server errors into the form state. The win is consistency: the same constraints apply while typing and when submitting. I also avoid validating on every keystroke for expensive schemas; validating on blur or submit usually feels better. When forms are predictable, product changes get easier because you’re not afraid to touch validation.