Skill v1.0.1
currentAutomated scan100/100+4 new
name: formik-patterns description: Formik form handling with validation patterns. Use when building forms, implementing validation, or handling form submission. risk: critical source: https://github.com/ChrisWiles/claude-code-showcase/tree/main/.claude/skills/formik-patterns source_repo: ChrisWiles/claude-code-showcase source_type: community date_added: 2026-07-01 license: MIT license_source: https://github.com/ChrisWiles/claude-code-showcase/blob/main/LICENSE metadata: version: "1.0.1"
Formik Patterns
When to Use
Use this skill when you need formik form handling with validation patterns. Use when building forms, implementing validation, or handling form submission.
Basic Form Setup
These are React Native-style/custom UI fragments: VStack/Input/Button, generated GraphQL hooks, schemas and navigation are project dependencies, not Formik exports. Web forms require DOM event/submit adapters. Client validation is UX only; validate/authenticate/authorize again on the server, redact mutation errors and never log passwords. enableReinitialize resets dirty edits when initial values change; prompt or merge deliberately before refreshing an edited record.
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 (<VStack gap="$4"><Inputlabel="Email"value={formik.values.email}onChangeText={formik.handleChange('email')}onBlur={formik.handleBlur('email')}error={formik.touched.email ? formik.errors.email : undefined}keyboardType="email-address"autoCapitalize="none"/><Inputlabel="Password"value={formik.values.password}onChangeText={formik.handleChange('password')}onBlur={formik.handleBlur('password')}error={formik.touched.password ? formik.errors.password : undefined}secureTextEntry/><ButtononPress={formik.handleSubmit}isDisabled={!formik.isValid || formik.isSubmitting}isLoading={formik.isSubmitting}>Login</Button></VStack>);};
Validation Schemas
Common Patterns
import * as yup from 'yup';email: yup.string().email('Invalid email address').required('Email is required')// Password with requirementspassword: 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 passwordconfirmPassword: yup.string().oneOf([yup.ref('password')], 'Passwords must match').required('Please confirm password')// Phone numberphone: yup.string().matches(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number').required('Phone is required')// Optional field with validation when presentwebsite: yup.string().url('Must be a valid URL').nullable()// Number with rangequantity: yup.number().min(1, 'Minimum 1').max(100, 'Maximum 100').required('Quantity required')// Array with minimum itemstags: 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<Input label="Email" {...getFieldProps('email')} />
Select/Picker Helper
<Selectlabel="Country"value={formik.values.country}onValueChange={(value) => 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 (<VStack gap="$4">{/* Form fields */}<ButtononPress={formik.handleSubmit}isDisabled={!formik.isValid || formik.isSubmitting}isLoading={formik.isSubmitting}>Create</Button></VStack>);};
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 changesvalidationSchema,onSubmit: async (values) => {await updateItem({variables: { id: item.id, input: values },});},});// Track if form has changesconst hasChanges = formik.dirty;return (<VStack gap="$4">{/* Form fields */}<ButtononPress={formik.handleSubmit}isDisabled={!hasChanges || !formik.isValid || formik.isSubmitting}isLoading={formik.isSubmitting}>Save Changes</Button></VStack>);};
Form State Helpers
const {values, // Current form valueserrors, // Validation errorstouched, // Fields that have been touchedisValid, // Form passes validationisSubmitting, // Submit in progressdirty, // Values differ from initialhandleSubmit, // Submit handlerhandleChange, // Change handlerhandleBlur, // Blur handlersetFieldValue, // Set single fieldsetFieldTouched, // Mark field touchedresetForm, // Reset to initial valuessetSubmitting, // Control submitting state} = formik;
Multi-Step Forms
const MultiStepForm = () => {const [step, setStep] = useState(0);const formik = useFormik({initialValues: {// Step 1name: '',email: '',// Step 2address: '',city: '',// Step 3paymentMethodToken: '', // token from a payment provider’s hosted field; never raw card data},validationSchema: stepSchemas[step],onSubmit: async (values) => {if (step < steps.length - 1) {setStep(step + 1);} else {// Validate the complete order schema here; step-only validation is insufficient.await finalOrderSchema.validate(values, { abortEarly: false });await submitOrder(values);}},});return (<VStack>{step === 0 && <PersonalInfoStep formik={formik} />}{step === 1 && <AddressStep formik={formik} />}{step === 2 && <PaymentStep formik={formik} />}<HStack gap="$4">{step > 0 && (<Button variant="outline" onPress={() => setStep(step - 1)}>Back</Button>)}<ButtononPress={formik.handleSubmit}isDisabled={!formik.isValid || formik.isSubmitting}isLoading={formik.isSubmitting}>{step < steps.length - 1 ? 'Next' : 'Submit'}</Button></HStack></VStack>);};
Anti-Patterns
// WRONG - Not showing validation errors<Inputvalue={formik.values.email}onChangeText={formik.handleChange('email')}/>// CORRECT - Show errors when touched<Inputvalue={formik.values.email}onChangeText={formik.handleChange('email')}onBlur={formik.handleBlur('email')}error={formik.touched.email ? formik.errors.email : undefined}/>// WRONG - Submit button always enabled<Button onPress={formik.handleSubmit}>Submit</Button>// CORRECT - Disabled when invalid or submitting<ButtononPress={formik.handleSubmit}isDisabled={!formik.isValid || formik.isSubmitting}isLoading={formik.isSubmitting}>Submit</Button>// WRONG - No error handling on mutationonSubmit: async (values) => {await createItem({ variables: { input: values } });}// CORRECT - Handle errorsonSubmit: 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
Limitations
- Use this skill only when the task clearly matches its upstream source and local project context.
- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.
- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.