typescript
73 lines · 2 tabs
Maya Patel
Jan 2026
2 tabs
import { z } from 'zod'
export const postSchema = z.object({
title: z
.string()
.min(5, 'Title must be at least 5 characters')
.max(100, 'Title must be at most 100 characters'),
body: z.string().min(50, 'Body must be at least 50 characters'),
status: z.enum(['draft', 'published'], {
errorMap: () => ({ message: 'Invalid status' }),
}),
tags: z.array(z.string()).max(5, 'Maximum 5 tags allowed').optional(),
})
export type PostFormData = z.infer<typeof postSchema>
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { postSchema, PostFormData } from '@/schemas/postSchema'
interface PostFormProps {
onSubmit: (data: PostFormData) => Promise<void>
defaultValues?: Partial<PostFormData>
}
export function PostForm({ onSubmit, defaultValues }: PostFormProps) {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<PostFormData>({
resolver: zodResolver(postSchema),
defaultValues,
})
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<label htmlFor="title" className="block text-sm font-medium mb-1">
Title
</label>
<input {...register('title')} id="title" className="w-full px-3 py-2 border rounded" />
{errors.title && <p className="text-red-600 text-sm mt-1">{errors.title.message}</p>}
</div>
<div>
<label htmlFor="body" className="block text-sm font-medium mb-1">
Body
</label>
<textarea {...register('body')} id="body" rows={10} className="w-full px-3 py-2 border rounded" />
{errors.body && <p className="text-red-600 text-sm mt-1">{errors.body.message}</p>}
</div>
<div>
<label htmlFor="status" className="block text-sm font-medium mb-1">
Status
</label>
<select {...register('status')} id="status" className="w-full px-3 py-2 border rounded">
<option value="draft">Draft</option>
<option value="published">Published</option>
</select>
{errors.status && <p className="text-red-600 text-sm mt-1">{errors.status.message}</p>}
</div>
<button
type="submit"
disabled={isSubmitting}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
>
{isSubmitting ? 'Saving...' : 'Save Post'}
</button>
</form>
)
}
2 files · typescript
Explain with highlit
Combining React Hook Form with Zod provides type-safe form validation with a schema-first approach. Zod schemas define the shape and rules for form data, and the zodResolver bridges them to React Hook Form. TypeScript infers form types from schemas automatically, ensuring consistency between validation and TypeScript types. I define schemas once and use them for both client-side validation and API request typing. Custom error messages integrate cleanly with React Hook Form's error display. This pattern eliminates duplicate validation logic between frontend and backend when I generate Zod schemas from Rails model validations. The combination delivers excellent DX with minimal boilerplate.
Related snips
typescript
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
typescript
reliability
retry
by codesnips
2 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
react
axios
api
by Maya Patel
1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
interface FilterState {
search: string
category: string | null
Zustand for lightweight state management
react
zustand
state-management
by Maya Patel
2 tabs
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Example</title>
<style>
HTML forms with validation and accessibility
html
forms
validation
by Alex Chang
1 tab
javascript
import { Application } from "@hotwired/stimulus"
import FormSubmitController from "./controllers/form_submit_controller"
const application = Application.start()
application.debug = false
Disable submit button while Turbo form is submitting
rails
hotwire
stimulus
by codesnips
3 tabs
javascript
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static values = {
url: String,
delay: { type: Number, default: 800 },
Stimulus: autosave draft with Turbo-friendly requests
rails
stimulus
hotwire
by codesnips
3 tabs
Share this code
Here's the card — post it anywhere.