| name | react_expert |
| description | React specialist for production-grade web applications. Invoke for component architecture, hooks, state management, Server Components, performance. Keywords: React, JSX, hooks, useState, useEffect, use(), Suspense, RSC. |
React Expert
Senior React specialist with deep expertise in React 19, Server Components, and production-grade application architecture.
Role Definition
You are a senior React engineer with 10+ years of frontend experience. You specialize in React 19 patterns including Server Components, the use() hook, and form actions. You build accessible, performant applications with TypeScript and modern state management.
When to Use This Skill
- Building new React components or features
- Implementing state management (local, Context, Redux, Zustand)
- Optimizing React performance
- Setting up React project architecture
- Working with React 19 Server Components
- Implementing forms with React 19 actions
- Data fetching patterns with TanStack Query or
use()
🔄 Workflow
Kaynak: React 19 Documentation & TanStack Query Best Practices
Aşama 1: Architecture & Server/Client Split
Aşama 2: Implementation & Interaction
Aşama 3: Performance & A11y Verification
Kontrol Noktaları
| Aşama | Doğrulama |
|---|
| 1 | key prop'ları index yerine stabil ve benzersiz ID'ler mi? |
| 2 | Server Side Rendering (SSR) sırasında hydration error oluşuyor mu? |
| 3 | Side Effect'lerde (useEffect) cleanup fonksiyonu mevcut mu? |
React Expert v2.0 - With Workflow
Load detailed guidance based on context:
| Topic | Reference | Load When |
|---|
| Server Components | references/server-components.md | RSC patterns, Next.js App Router |
| React 19 | references/react-19-features.md | use() hook, useActionState, forms |
| State Management | references/state-management.md | Context, Zustand, Redux, TanStack |
| Hooks | references/hooks-patterns.md | Custom hooks, useEffect, useCallback |
| Performance | references/performance.md | memo, lazy, virtualization |
| Testing | references/testing-react.md | Testing Library, mocking |
Constraints
MUST DO
- Use TypeScript with strict mode
- Implement error boundaries for graceful failures
- Use
key props correctly (stable, unique identifiers)
- Clean up effects (return cleanup function)
- Use semantic HTML and ARIA for accessibility
- Memoize when passing callbacks/objects to memoized children
- Use Suspense boundaries for async operations
MUST NOT DO
- Mutate state directly
- Use array index as key for dynamic lists
- Create functions inside JSX (causes re-renders)
- Forget useEffect cleanup (memory leaks)
- Ignore React strict mode warnings
- Skip error boundaries in production
Output Templates
When implementing React features, provide:
- Component file with TypeScript types
- Test file if non-trivial logic
- Brief explanation of key decisions
Knowledge Reference
React 19, Server Components, use() hook, Suspense, TypeScript, TanStack Query, Zustand, Redux Toolkit, React Router, React Testing Library, Vitest/Jest, Next.js App Router, accessibility (WCAG)
Related Skills
- Fullstack Guardian - Full-stack feature implementation
- Playwright Expert - E2E testing for React apps
- Test Master - Comprehensive testing strategies
Merged Content from react-hook-form
name: react_expert
description: |
This skill provides comprehensive knowledge for building type-safe, validated forms in React using React Hook Form and Zod schema validation.
Use when: building forms with validation in React, integrating Zod schema validation with React Hook Form, using shadcn/ui Form or Field components, implementing client and server-side validation with a single schema, handling complex validation scenarios (nested objects, arrays, conditional fields, async validation), building multi-step forms or wizards, implementing dynamic form fields with useFieldArray, optimizing form performance and re-renders, ensuring accessible form error handling, or debugging form validation issues.
Keywords: react-hook-form, useForm, zod validation, zodResolver, @hookform/resolvers, form schema, register, handleSubmit, formState, useFieldArray, useWatch, useController, Controller, shadcn form, Field component, client server validation, nested validation, array field validation, dynamic fields, multi-step form, async validation, zod refine, z.infer, form error handling, uncontrolled to controlled, resolver not found, schema validation error
license: MIT
React Hook Form + Zod Validation
Status: Production Ready ✅
Last Updated: 2025-10-23
Dependencies: None (standalone)
Latest Versions: react-hook-form@7.65.0, zod@4.1.12, @hookform/resolvers@5.2.2
Quick Start (10 Minutes)
1. Install Packages
npm install react-hook-form@7.65.0 zod@4.1.12 @hookform/resolvers@5.2.2
Why These Packages:
- react-hook-form: Performant, flexible form library with minimal re-renders
- zod: TypeScript-first schema validation with type inference
- @hookform/resolvers: Adapter to connect Zod (and other validators) to React Hook Form
2. Create Your First Form
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
})
type LoginFormData = z.infer<typeof loginSchema>
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: '',
password: '',
},
})
const onSubmit = async (data: LoginFormData) => {
.(, data)
}
(
)
}
CRITICAL:
- Always set
defaultValues to prevent "uncontrolled to controlled" warnings
- Use
zodResolver(schema) to connect Zod validation
- Type form with
z.infer<typeof schema> for full type safety
- Validate on both client AND server (never trust client validation alone)
3. Add Server-Side Validation
import { z } from 'zod'
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
})
export async function loginHandler(req: Request) {
try {
const data = loginSchema.parse(await req.json())
return { success: true }
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, errors: error.flatten().fieldErrors }
}
throw error
}
}
Why Server Validation:
- Client validation can be bypassed (inspect element, Postman, curl)
- Server validation is your security layer
- Same Zod schema = single source of truth
- Type safety across frontend and backend
Core Concepts
useForm Hook Anatomy
const {
register,
handleSubmit,
watch,
formState,
setValue,
getValues,
reset,
trigger,
control,
} = useForm<FormData>({
resolver: zodResolver(schema),
mode: 'onSubmit',
defaultValues: {},
})
useForm Options:
| Option | Description | Default |
|---|
resolver | Validation resolver (e.g., zodResolver) | undefined |
mode | When to validate ('onSubmit', 'onChange', 'onBlur', 'all') | 'onSubmit' |
reValidateMode | When to re-validate after error | 'onChange' |
defaultValues | Initial form values | {} |
shouldUnregister | Unregister inputs when unmounted | false |
criteriaMode | Return all errors or first error only | 'firstError' |
Form Validation Modes:
onSubmit - Validate on submit (best performance, less responsive)
onChange - Validate on every change (live feedback, more re-renders)
onBlur - Validate when field loses focus (good balance)
all - Validate on submit, blur, and change (most responsive, highest cost)
Zod Schema Definition
import { z } from 'zod'
const stringSchema = z.string()
const numberSchema = z.number()
const booleanSchema = z.boolean()
const dateSchema = z.date()
const emailSchema = z.string().email('Invalid email')
const ageSchema = z.number().min(18, 'Must be 18+').max(120, 'Invalid age')
const usernameSchema = z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/)
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().int().positive(),
})
const tagsSchema = z.array(z.string())
const usersSchema = z.(userSchema)
optionalField = z.().()
nullableField = z.().()
nullishField = z.().()
withDefault = z.().()
statusSchema = z.([
z.(),
z.(),
z.(),
])
statusEnum = z.([, , ])
addressSchema = z.({
: z.(),
: z.(),
: z.().(),
})
profileSchema = z.({
: z.(),
: addressSchema,
})
passwordSchema = z.()
.(, { : })
.(, { : })
.(, { : })
Type Inference:
const userSchema = z.object({
name: z.string(),
age: z.number(),
})
type User = z.infer<typeof userSchema>
Zod Refinements (Custom Validation)
const passwordConfirmSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
})
const signupSchema = z.object({
username: z.string(),
email: z.string().email(),
age: z.number(),
})
.refine((data) => data.username !== data.email.split('@')[0], {
message: 'Username cannot be your email prefix',
path: ['username'],
})
.refine((data) => data.age >= 18, {
message: 'Must be 18 or older',
path: ['age'],
})
usernameSchema = z.().( (username) => {
response = ()
{ available } = response.()
available
}, {
: ,
})
Zod Transforms (Data Manipulation)
const ageSchema = z.string().transform((val) => parseInt(val, 10))
const uppercaseSchema = z.string().transform((val) => val.toUpperCase())
const dateSchema = z.string().transform((val) => new Date(val))
const trimmedSchema = z.string().transform((val) => val.trim())
const userInputSchema = z.object({
email: z.string().email().transform((val) => val.toLowerCase()),
tags: z.string().transform((val) => val.split(',').map(tag => tag.trim())),
})
positiveNumberSchema = z.()
.( (val))
.( !(val), { : })
.( val > , { : })
zodResolver Integration
import { zodResolver } from '@hookform/resolvers/zod'
const form = useForm<FormData>({
resolver: zodResolver(schema),
})
What zodResolver Does:
- Takes your Zod schema
- Converts it to a format React Hook Form understands
- Provides validation function that runs on form submission
- Maps Zod errors to React Hook Form error format
- Preserves type safety with TypeScript inference
zodResolver Options:
import { zodResolver } from '@hookform/resolvers/zod'
const form = useForm({
resolver: zodResolver(schema, {
async: false,
raw: false,
}),
})
Form Registration Patterns
Pattern 1: Simple Input Registration
function BasicForm() {
const { register, handleSubmit } = useForm<FormData>({
resolver: zodResolver(schema),
})
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* Spread register result to input */}
<input {...register('email')} />
<input {...register('password')} />
{/* With custom props */}
<input
{...register('username')}
placeholder="Enter username"
className="input"
/>
</form>
)
}
What register() Returns:
{
onChange: (e) => void,
onBlur: (e) => void,
ref: (instance) => void,
name: string,
}
Pattern 2: Controller (for Custom Components)
Use Controller when the input doesn't expose ref (like custom components, React Select, date pickers, etc.):
import { Controller } from 'react-hook-form'
function FormWithCustomInput() {
const { control, handleSubmit } = useForm<FormData>({
resolver: zodResolver(schema),
})
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="category"
control={control}
render={({ field }) => (
<CustomSelect
{...field} // value, onChange, onBlur, ref
options={categoryOptions}
/>
)}
/>
{/* With more control */}
<Controller
name="dateOfBirth"
control={control}
render={({ field, fieldState }) => (
<div>
<DatePicker
selected={field.value}
onChange={field.onChange}
onBlur=
/>
{fieldState.error && (
{fieldState.error.message}
)}
)}
/>
)
}
When to Use Controller:
- ✅ Third-party UI libraries (React Select, Material-UI, Ant Design, etc.)
- ✅ Custom components that don't expose ref
- ✅ Components that don't use onChange (like checkboxes with custom handlers)
- ✅ Need fine-grained control over field behavior
When NOT to Use Controller:
- ❌ Standard HTML inputs (use
register instead - it's simpler and faster)
- ❌ When performance is critical (Controller adds minimal overhead)
Pattern 3: useController (Reusable Controlled Inputs)
import { useController } from 'react-hook-form'
function CustomInput({ name, control, label }) {
const {
field,
fieldState: { error },
} = useController({
name,
control,
defaultValue: '',
})
return (
<div>
<label>{label}</label>
<input {...field} />
{error && <span>{error.message}</span>}
</div>
)
}
function MyForm() {
const { control, handleSubmit } = useForm({
resolver: zodResolver(schema),
})
return (
<form onSubmit={handleSubmit(onSubmit)}>
<CustomInput name="email" control={control} label="Email" />
<CustomInput name="username" control= = />
)
}
Error Handling
Displaying Errors
function FormWithErrors() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
})
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<input {...register('email')} aria-invalid={errors.email ? 'true' : 'false'} />
{/* Simple error display */}
{errors.email && <span>{errors.email.message}</span>}
{/* Accessible error display */}
{errors.email && (
<span role="alert" className="error">
{errors.email.message}
</span>
)}
{/* Error with icon */}
{errors.email && (
<div role="alert" className="error">
<ErrorIcon />
<span>{errors.email.message}</span>
</div>
)}
</div>
</>
)
}
Error Object Structure
{
email: {
type: 'invalid_string',
message: 'Invalid email address',
},
password: {
type: 'too_small',
message: 'Password must be at least 8 characters',
},
address: {
street: {
type: 'invalid_type',
message: 'Expected string, received undefined',
},
},
}
Form-Level Validation Errors
const schema = z.object({
password: z.string().min(8),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ['confirmPassword'],
})
.refine((data) => someCondition, {
message: 'Form validation failed',
})
const { formState: { errors } } = useForm()
errors.root?.message
Server Errors Integration
function FormWithServerErrors() {
const { register, handleSubmit, setError, formState: { errors } } = useForm({
resolver: zodResolver(schema),
})
const onSubmit = async (data) => {
try {
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data),
})
if (!response.ok) {
const { errors: serverErrors } = await response.json()
Object.entries(serverErrors).forEach(([field, message]) => {
setError(field, {
type: 'server',
message,
})
})
return
}
} catch (error) {
setError('root', {
type: 'server',
message: 'An error occurred. Please try again.',
})
}
}
(
)
}
Advanced Patterns
Dynamic Form Fields (useFieldArray)
import { useForm, useFieldArray } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
const contactSchema = z.object({
contacts: z.array(
z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email'),
})
).min(1, 'At least one contact is required'),
})
type ContactFormData = z.infer<typeof contactSchema>
function ContactListForm() {
const { register, control, handleSubmit, formState: { errors } } = useForm<ContactFormData>({
resolver: zodResolver(contactSchema),
defaultValues: {
contacts: [{ name: '', email: '' }],
},
})
const { fields, append, remove } = useFieldArray({
control,
name: 'contacts',
})
return (
)
}
useFieldArray API:
fields - Array of field items with unique IDs
append(value) - Add new item to end
prepend(value) - Add new item to beginning
insert(index, value) - Insert item at index
remove(index) - Remove item at index
update(index, value) - Update item at index
replace(values) - Replace entire array
Async Validation with Debouncing
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { useDebouncedCallback } from 'use-debounce'
const usernameSchema = z.string().min(3).refine(async (username) => {
const response = await fetch(`/api/check-username?username=${username}`)
const { available } = await response.json()
return available
}, {
message: 'Username is already taken',
})
function AsyncValidationForm() {
const { register, handleSubmit, trigger, formState: { errors, isValidating } } = useForm({
resolver: zodResolver(z.object({ username: usernameSchema })),
mode: 'onChange',
})
const debouncedValidation = useDebouncedCallback(() => {
trigger()
}, )
(
)
}
Multi-Step Form (Wizard)
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
const step1Schema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email'),
})
const step2Schema = z.object({
address: z.string().min(1, 'Address is required'),
city: z.string().min(1, 'City is required'),
})
const step3Schema = z.object({
cardNumber: z.string().regex(/^\d{16}$/, 'Invalid card number'),
cvv: z.string().regex(/^\d{3,4}$/, 'Invalid CVV'),
})
const fullSchema = step1Schema.(step2Schema).(step3Schema)
= z.< fullSchema>
() {
[step, setStep] = ()
{ register, handleSubmit, trigger, : { errors } } = useForm<>({
: (fullSchema),
: ,
})
= () => {
: (keyof )[] = []
(step === ) {
fieldsToValidate = [, ]
} (step === ) {
fieldsToValidate = [, ]
}
isValid = (fieldsToValidate)
(isValid) {
(step + )
}
}
= () => (step - )
= () => {
.(, data)
}
(
)
}
Conditional Validation
import { z } from 'zod'
const formSchema = z.discriminatedUnion('accountType', [
z.object({
accountType: z.literal('personal'),
name: z.string().min(1),
}),
z.object({
accountType: z.literal('business'),
companyName: z.string().min(1),
taxId: z.string().regex(/^\d{9}$/),
}),
])
const conditionalSchema = z.object({
hasDiscount: z.boolean(),
discountCode: z.string().optional(),
}).refine((data) => {
if (data.hasDiscount && !data.discountCode) {
return false
}
return true
}, {
message: 'Discount code is required when discount is enabled',
path: [],
})
shadcn/ui Integration
Using Form Component (Legacy)
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
const formSchema = z.object({
username: z.string().min(2, 'Username must be at least 2 characters'),
email: z.string().email('Invalid email address'),
})
function ProfileForm() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: '',
email: '',
},
})
return (
<Form {}>
(
Username
This is your public display name.
)}
/>
(
Email
)}
/>
Submit
)
}
Note: shadcn/ui states "We are not actively developing the Form component anymore." They recommend using the Field component for new implementations.
Using Field Component (Recommended)
Check shadcn/ui documentation for the latest Field component API as it's the actively maintained approach.
Performance Optimization
Form Mode Strategies
const form = useForm({
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const form = useForm({
mode: 'onBlur',
resolver: zodResolver(schema),
})
const form = useForm({
mode: 'onChange',
resolver: zodResolver(schema),
})
const form = useForm({
mode: 'all',
resolver: zodResolver(schema),
})
Controlled vs Uncontrolled Inputs
<input {...register('email')} />
<Controller
name="email"
control={control}
render={({ field }) => <Input {...field} />}
/>
Recommendation: Use register for standard inputs, Controller only when necessary (third-party components, custom behavior).
Isolation with Controller
function BadForm() {
const { watch } = useForm()
const values = watch()
return <div>{JSON.stringify(values)}</div>
}
function GoodForm() {
const { watch } = useForm()
const email = watch('email')
return <div>{email}</div>
}
shouldUnregister Flag
const form = useForm({
resolver: zodResolver(schema),
shouldUnregister: true,
})
When to use:
- ✅ Multi-step forms where steps have different fields
- ✅ Conditional fields that should not persist
- ✅ Want to clear data when component unmounts
When NOT to use:
- ❌ Want to preserve form data when toggling visibility
- ❌ Navigating between form sections (tabs, accordions)
Accessibility Best Practices
ARIA Attributes
function AccessibleForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
})
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="email">Email</label>
<input
id="email"
{...register('email')}
aria-invalid={errors.email ? 'true' : 'false'}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && (
<span id="email-error" role="alert">
{errors.email.message}
</span>
)}
</div>
</form>
)
}
Error Announcements
import { useEffect } from 'react'
function FormWithAnnouncements() {
const { formState: { errors, isSubmitted } } = useForm()
useEffect(() => {
if (isSubmitted && Object.keys(errors).length > 0) {
const errorCount = Object.keys(errors).length
const announcement = `Form submission failed with ${errorCount} error${errorCount > 1 ? 's' : ''}`
const liveRegion = document.createElement('div')
liveRegion.setAttribute('role', 'alert')
liveRegion.setAttribute('aria-live', 'assertive')
liveRegion.textContent = announcement
document.body.appendChild(liveRegion)
setTimeout(() => {
document.body.removeChild(liveRegion)
}, )
}
}, [errors, isSubmitted])
(
)
}
Focus Management
import { useRef, useEffect } from 'react'
function FormWithFocus() {
const { handleSubmit, formState: { errors } } = useForm()
const firstErrorRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (Object.keys(errors).length > 0) {
firstErrorRef.current?.focus()
}
}, [errors])
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('email')}
ref={errors.email ? firstErrorRef : undefined}
/>
</form>
)
}
Critical Rules
Always Do
✅ Set defaultValues to prevent "uncontrolled to controlled" warnings
const form = useForm({
defaultValues: { email: '', password: '' },
})
✅ Use zodResolver for Zod integration
const form = useForm({
resolver: zodResolver(schema),
})
✅ Type forms with z.infer
type FormData = z.infer<typeof schema>
✅ Validate on both client AND server
const form = useForm({ resolver: zodResolver(schema) })
const data = schema.parse(await req.json())
✅ Use formState.errors for error display
{errors.email && <span role="alert">{errors.email.message}</span>}
✅ Add ARIA attributes for accessibility
<input
{...register('email')}
aria-invalid={errors.email ? 'true' : 'false'}
aria-describedby="email-error"
/>
✅ Use field.id for useFieldArray keys
{fields.map((field) => <div key={field.id}>{/* ... */}</div>)}
✅ Debounce async validation
const debouncedValidation = useDebouncedCallback(() => trigger('username'), 500)
Never Do
❌ Skip server-side validation (security vulnerability!)
const form = useForm({ resolver: zodResolver(schema) })
const form = useForm({ resolver: zodResolver(schema) })
❌ Use Zod v4 without checking type inference
❌ Forget to spread {...field} in Controller
<Controller render={({ field }) => <Input value={field.value} />} />
<Controller render={({ field }) => <Input {...field} />} />
❌ Mutate form values directly
const values = getValues()
values.email = 'new@email.com'
setValue('email', 'new@email.com')
❌ Use inline validation without debouncing
const form = useForm({ mode: 'onChange' })
const debouncedTrigger = useDebouncedCallback(() => trigger(), 500)
❌ Mix controlled and uncontrolled inputs
<input {...register('email')} value={email} onChange={setEmail} />
<input {...register('email')} />
<Controller render={({ field }) => <Input {...field} />} />
❌ Use index as key in useFieldArray
{fields.map((field, index) => <div key={index}>{/* ... */}</div>)}
{fields.map((field) => <div key={field.id}>{/* ... */}</div>)}
❌ Forget defaultValues for all fields
const form = useForm({
resolver: zodResolver(schema),
})
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { email: '', password: '', remember: false },
})
Known Issues Prevention
This skill prevents 12 documented issues:
Issue #1: Zod v4 Type Inference Errors
Error: Type inference doesn't work correctly with Zod v4
Source: GitHub Issue #13109
Why It Happens: Zod v4 changed how types are inferred
Prevention: Use correct type patterns: type FormData = z.infer<typeof schema>
Issue #2: Uncontrolled to Controlled Warning
Error: "A component is changing an uncontrolled input to be controlled"
Source: React documentation
Why It Happens: Not setting defaultValues causes undefined -> value transition
Prevention: Always set defaultValues for all fields
Issue #3: Nested Object Validation Errors
Error: Errors for nested fields don't display correctly
Source: Common React Hook Form issue
Why It Happens: Accessing nested errors incorrectly
Prevention: Use optional chaining: errors.address?.street?.message
Issue #4: Array Field Re-renders
Error: Form re-renders excessively with array fields
Source: Performance issue
Why It Happens: Not using field.id as key
Prevention: Use key={field.id} in useFieldArray map
Issue #5: Async Validation Race Conditions
Error: Multiple validation requests cause conflicting results
Source: Common async pattern issue
Why It Happens: No debouncing or request cancellation
Prevention: Debounce validation and cancel pending requests
Issue #6: Server Error Mapping
Error: Server validation errors don't map to form fields
Source: Integration issue
Why It Happens: Server error format doesn't match React Hook Form format
Prevention: Use setError() to map server errors to fields
Issue #7: Default Values Not Applied
Error: Form fields don't show default values
Source: Common mistake
Why It Happens: defaultValues set after form initialization
Prevention: Set defaultValues in useForm options, not useState
Issue #8: Controller Field Not Updating
Error: Custom component doesn't update when value changes
Source: Common Controller issue
Why It Happens: Not spreading {...field} in render function
Prevention: Always spread {...field} to custom component
Issue #9: useFieldArray Key Warnings
Error: React warning about duplicate keys in list
Source: React list rendering
Why It Happens: Using array index as key instead of field.id
Prevention: Use field.id: key={field.id}
Issue #10: Schema Refinement Error Paths
Error: Custom validation errors appear at wrong field
Source: Zod refinement behavior
Why It Happens: Not specifying path in refinement options
Prevention: Add path option: refine(..., { message: '...', path: ['fieldName'] })
Issue #11: Transform vs Preprocess Confusion
Error: Data transformation doesn't work as expected
Source: Zod API confusion
Why It Happens: Using wrong method for use case
Prevention: Use transform for output transformation, preprocess for input transformation
Issue #12: Multiple Resolver Conflicts
Error: Form validation doesn't work with multiple resolvers
Source: Configuration error
Why It Happens: Trying to use multiple validation libraries
Prevention: Use single resolver (zodResolver), combine schemas if needed
Templates
See the templates/ directory for working examples:
- basic-form.tsx - Simple login/signup form
- advanced-form.tsx - Nested objects, arrays, conditional fields
- shadcn-form.tsx - shadcn/ui Form component integration
- server-validation.ts - Server-side validation with same schema
- async-validation.tsx - Async validation with debouncing
- dynamic-fields.tsx - useFieldArray for adding/removing items
- multi-step-form.tsx - Wizard with per-step validation
- custom-error-display.tsx - Custom error formatting
- package.json - Complete dependencies
References
See the references/ directory for deep-dive documentation:
- zod-schemas-guide.md - Comprehensive Zod schema patterns
- rhf-api-reference.md - Complete React Hook Form API
- error-handling.md - Error messages, formatting, accessibility
- accessibility.md - WCAG compliance, ARIA attributes
- performance-optimization.md - Form modes, validation strategies
- shadcn-integration.md - shadcn/ui Form vs Field components
- top-errors.md - 12 common errors with solutions
- links-to-official-docs.md - Organized documentation links
Official Documentation
License: MIT
Last Verified: 2025-10-23
Maintainer: Jeremy Dawes (jeremy@jezweb.net)
Merged Content from shadcn-ui
name: react_expert
description: shadcn/ui component library kurulum, customization ve best practices rehberi.
metadata:
skillport:
category: development
tags:
- shadcn
- ui
- components
- react
🎨 shadcn/ui
shadcn/ui component library rehberi.
📋 Kurulum
npx shadcn-ui@latest init
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card
npx shadcn-ui@latest add dialog
🔧 Temel Components
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
<Button variant="default">Default</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button size="default">Default
🎨 Customization
globals.css
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
}
}
📦 Sık Kullanılan
| Component | Kullanım |
|---|
| Button | Actions |
| Card | Content containers |
| Dialog | Modals |
| Input | Form inputs |
| Select | Dropdowns |
| Table | Data display |
| Tabs | Navigation |
| Toast | Notifications |
shadcn/ui v1.0