--- name: form-handling-mobile description: React Hook Form and Zod for React Native forms. Use when implementing forms. --- # Form Handling Mobile Skill This skill covers React Hook Form with Zod validation for React Native. ## When to Use Use this skill when: - Building login/signup forms - Creating data entry forms - Implementing form validation - Handling complex form state ## Core Principle **CONTROLLED VALIDATION** - Use Zod for schema validation, React Hook Form for state. ## Installation ```bash npm install react-hook-form @hookform/resolvers zod ``` ## Basic Form ```typescript import { View, Text, TextInput, TouchableOpacity } from 'react-native'; import { useForm, Controller } 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; export function LoginForm(): React.ReactElement { const { control, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(loginSchema), defaultValues: { email: '', password: '', }, }); const onSubmit = (data: LoginFormData) => { console.log(data); }; return ( Email ( )} /> {errors.email && ( {errors.email.message} )} Password ( )} /> {errors.password && ( {errors.password.message} )} Sign In ); } ``` ## Complex Validation Schema ```typescript const signupSchema = z .object({ email: z.string().email('Invalid email'), username: z .string() .min(3, 'Username must be at least 3 characters') .max(20, 'Username must be at most 20 characters') .regex(/^[a-zA-Z0-9_]+$/, 'Only letters, numbers, and underscores'), password: z .string() .min(8, 'Password must be at least 8 characters') .regex(/[A-Z]/, 'Must contain uppercase letter') .regex(/[a-z]/, 'Must contain lowercase letter') .regex(/[0-9]/, 'Must contain number'), confirmPassword: z.string(), acceptTerms: z.boolean().refine((val) => val === true, { message: 'You must accept the terms', }), }) .refine((data) => data.password === data.confirmPassword, { message: 'Passwords do not match', path: ['confirmPassword'], }); ``` ## Reusable Input Component ```typescript import { Control, Controller, FieldValues, Path } from 'react-hook-form'; interface FormInputProps { control: Control; name: Path; label: string; placeholder?: string; secureTextEntry?: boolean; keyboardType?: 'default' | 'email-address' | 'numeric' | 'phone-pad'; autoCapitalize?: 'none' | 'sentences' | 'words' | 'characters'; error?: string; } export function FormInput({ control, name, label, placeholder, secureTextEntry, keyboardType = 'default', autoCapitalize = 'sentences', error, }: FormInputProps): React.ReactElement { return ( {label} ( )} /> {error && ( {error} )} ); } // Usage ``` ## With Gluestack-ui ```typescript import { FormControl, FormControlLabel, FormControlLabelText, FormControlError, FormControlErrorText, Input, InputField, } from '@gluestack-ui/themed'; import { Controller, useForm } from 'react-hook-form'; export function StyledLoginForm(): React.ReactElement { const { control, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(loginSchema), }); return ( Email ( )} /> {errors.email?.message} ); } ``` ## Form with Mutation ```typescript import { useMutation } from '@tanstack/react-query'; import { useRouter } from 'expo-router'; export function LoginFormWithMutation(): React.ReactElement { const router = useRouter(); const { mutate: login, isPending } = useLoginMutation(); const { control, handleSubmit, formState: { errors }, setError, } = useForm({ resolver: zodResolver(loginSchema), }); const onSubmit = (data: LoginFormData) => { login(data, { onSuccess: () => { router.replace('/(tabs)'); }, onError: (error) => { setError('root', { message: error.message || 'Login failed', }); }, }); }; return ( {errors.root && ( {errors.root.message} )} {/* Form fields */} {isPending ? 'Signing in...' : 'Sign In'} ); } ``` ## Checkbox and Switch ```typescript const preferencesSchema = z.object({ emailNotifications: z.boolean(), pushNotifications: z.boolean(), newsletter: z.boolean(), }); ( Email Notifications )} /> ``` ## Select/Picker ```typescript import { Picker } from '@react-native-picker/picker'; ( )} /> ``` ## Watch and Dynamic Fields ```typescript function DynamicForm(): React.ReactElement { const { control, watch } = useForm(); const showAdditionalFields = watch('hasAccount'); return ( ( I have an account )} /> {showAdditionalFields && ( )} ); } ``` ## Notes - Use Zod for type-safe validation - Create reusable form components - Handle loading states during submission - Show validation errors inline - Use setError for server errors - Test form behavior on both platforms