--- name: formik-patterns description: Formik form handling with validation patterns. Use when building forms, implementing validation, or handling form submission. --- # Formik Patterns ## Basic Form Setup ```tsx import { useFormik } from 'formik'; import * as yup from 'yup'; const validationSchema = yup.object({ email: yup.string().email('Invalid email').required('Email is required'), password: yup.string().min(8, 'Min 8 characters').required('Password is required'), }); const LoginForm = () => { const formik = useFormik({ initialValues: { email: '', password: '', }, validationSchema, onSubmit: async (values) => { await loginMutation({ variables: { input: values } }); }, }); return ( ); }; ``` ## Validation Schemas ### Common Patterns ```typescript import * as yup from 'yup'; // Email email: yup.string() .email('Invalid email address') .required('Email is required') // Password with requirements password: yup.string() .min(8, 'Must be at least 8 characters') .matches(/[a-z]/, 'Must contain lowercase letter') .matches(/[A-Z]/, 'Must contain uppercase letter') .matches(/[0-9]/, 'Must contain number') .required('Password is required') // Confirm password confirmPassword: yup.string() .oneOf([yup.ref('password')], 'Passwords must match') .required('Please confirm password') // Phone number phone: yup.string() .matches(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number') .required('Phone is required') // Optional field with validation when present website: yup.string() .url('Must be a valid URL') .nullable() // Number with range quantity: yup.number() .min(1, 'Minimum 1') .max(100, 'Maximum 100') .required('Quantity required') // Array with minimum items tags: yup.array() .of(yup.string()) .min(1, 'Select at least one tag') ``` ### Conditional Validation ```typescript const schema = yup.object({ hasCompany: yup.boolean(), companyName: yup.string().when('hasCompany', { is: true, then: (schema) => schema.required('Company name required'), otherwise: (schema) => schema.nullable(), }), }); ``` ## Form Field Helpers ### Input Helper ```tsx const getFieldProps = (name: keyof typeof formik.values) => ({ value: formik.values[name], onChangeText: formik.handleChange(name), onBlur: formik.handleBlur(name), error: formik.touched[name] ? formik.errors[name] : undefined, }); // Usage ``` ### Select/Picker Helper ```tsx // CORRECT - Show errors when touched // WRONG - Submit button always enabled // CORRECT - Disabled when invalid or submitting // WRONG - No error handling on mutation onSubmit: async (values) => { await createItem({ variables: { input: values } }); } // CORRECT - Handle errors onSubmit: async (values, { setSubmitting }) => { try { await createItem({ variables: { input: values } }); } catch (error) { toast.error({ title: 'Failed to save' }); } finally { setSubmitting(false); } } ``` ## Integration with Other Skills - **graphql-schema**: Mutation submission patterns - **react-ui-patterns**: Loading/error states - **testing-patterns**: Test form validation and submission