All skills
Skillintermediate
Formik Patterns
```tsx import { useFormik } from 'formik'; import * as yup from 'yup';
Claude Code Knowledge Pack7/10/2026
Overview
Formik Patterns
Basic Form Setup
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 (
Login
);
};
Validation Schemas
Common Patterns
// 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
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
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
formik.setFieldValue('country', value)}
error={formik.touched.country ? formik.errors.country : undefined}
options={countryOptions}
/>
Form Submission with GraphQL
const CreateItemForm = () => {
const [createItem] = useCreateItemMutation({
onCompleted: () => {
toast.success({ title: 'Item created' });
navigation.goBack();
},
onError: (error) => {
console.error('createItem failed:', error);
toast.error({ title: 'Failed to create item' });
},
});
const formik = useFormik({
initialValues: { name: '', description: '' },
validationSchema,
onSubmit: async (values, { setSubmitting }) => {
try {
await createItem({ variables: { input: values } });
} finally {
setSubmitting(false);
}
},
});
return (
Create
);
};
Edit Form with Initial Values
const EditItemForm = ({ item }: { item: Item }) => {
const [updateItem] = useUpdateItemMutation({
onCompleted: () => toast.success({ title: 'Saved' }),
onError: (error) => {
console.error('updateItem failed:', error);
toast.error({ title: 'Save failed' });
},
});
const formik = useFormik({
initialValues: {
name: item.name,
description: item.description ?? '',
},
enableReinitialize: true, // Update when item prop changes
validationSchema,
onSubmit: async (values) => {
await updateItem({
variables: { id: item.id, input: values },
});
},
});
// Track if form has changes
const hasChanges = formik.dirty;
return (
Save Changes
);
};
Form State Helpers
const {
values, // Current form values
errors, // Validation errors
touched, // Fields that have been touched
isValid, // Form passes validation
isSubmitting, // Submit in progress
dirty, // Values differ from initial
handleSubmit, // Submit handler
handleChange, // Change handler
handleBlur, // Blur handler
setFieldValue, // Set single field
setFieldTouched, // Mark field touched
resetForm, // Reset to initial values
setSubmitting, // Control submitting state
} = formik;
Multi-Step Forms
const MultiStepForm = () => {
const [step, setStep] = useState(0);
const formik = useFormik({
initialValues: {
// Step 1
name: '',
email: '',
// Step 2
address: '',
city: '',
// Step 3
cardNumber: '',
},
validationSchema: stepSchemas[step],
onSubmit: async (values) => {
if (step < steps.length - 1) {
setStep(step + 1);
} else {
await submitOrder(values);
}
},
});
return (
{step === 0 && }
{step === 1 && }
{step === 2 && }
{step > 0 && (
setStep(step - 1)}>
Back
)}
{step < steps.length - 1 ? 'Next' : 'Submit'}
);
};
Anti-Patterns
// WRONG - Not showing validation errors
// CORRECT - Show errors when touched
// WRONG - Submit button always enabled
Submit
// CORRECT - Disabled when invalid or submitting
Submit
// 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