ChatGPT Claude Codex CLI Cohere Cursor DeepSeek Gemini GitHub Copilot GLM Grok Kimi Llama MiniMax Mistral OpenAI opencode Skill

formik-patterns

Formik form handling with validation patterns. Use when building forms, implementing validation, or handling form submission.

LLM Mart · 0 points · 24 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download sickn33-agentic-awesome-skills-skills_formik-patterns-286166a.zip · 2 KB
Part of sickn33/agentic-awesome-skills — 427 skills
This skill couldn't be refreshed from GitHub on the last check — you're seeing the last imported snapshot.

Install

skills CLI npx skills add https://github.com/sickn33/agentic-awesome-skills/tree/main/skills/formik-patterns
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install sickn33-agentic-awesome-skills@llmmart
Git git clone https://github.com/sickn33/agentic-awesome-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole sickn33/agentic-awesome-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

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

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">
      <Input
        label="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"
      />

      <Input
        label="Password"
        value={formik.values.password}
        onChangeText={formik.handleChange('password')}
        onBlur={formik.handleBlur('password')}
        error={formik.touched.password ? formik.errors.password : undefined}
        secureTextEntry
      />

      <Button
        onPress={formik.handleSubmit}
        isDisabled={!formik.isValid || formik.isSubmitting}
        isLoading={formik.isSubmitting}
      >
        Login
      </Button>
    </VStack>
  );
};

Validation Schemas

Common Patterns

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

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

<Select
  label="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 */}
      <Button
        onPress={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 changes
    validationSchema,
    onSubmit: async (values) => {
      await updateItem({
        variables: { id: item.id, input: values },
      });
    },
  });

  // Track if form has changes
  const hasChanges = formik.dirty;

  return (
    <VStack gap="$4">
      {/* Form fields */}
      <Button
        onPress={formik.handleSubmit}
        isDisabled={!hasChanges || !formik.isValid || formik.isSubmitting}
        isLoading={formik.isSubmitting}
      >
        Save Changes
      </Button>
    </VStack>
  );
};

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 (
    <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>
        )}
        <Button
          onPress={formik.handleSubmit}
          isDisabled={!formik.isValid}
          isLoading={formik.isSubmitting}
        >
          {step < steps.length - 1 ? 'Next' : 'Submit'}
        </Button>
      </HStack>
    </VStack>
  );
};

Anti-Patterns

// WRONG - Not showing validation errors
<Input
  value={formik.values.email}
  onChangeText={formik.handleChange('email')}
/>

// CORRECT - Show errors when touched
<Input
  value={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
<Button
  onPress={formik.handleSubmit}
  isDisabled={!formik.isValid || formik.isSubmitting}
  isLoading={formik.isSubmitting}
>
  Submit
</Button>


// 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

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.
Files (agentic-awesome-skills)
  • SKILL.md 9.2 KB
    ---
    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
    ---
    
    # 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
    
    ```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 (
        <VStack gap="$4">
          <Input
            label="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"
          />
    
          <Input
            label="Password"
            value={formik.values.password}
            onChangeText={formik.handleChange('password')}
            onBlur={formik.handleBlur('password')}
            error={formik.touched.password ? formik.errors.password : undefined}
            secureTextEntry
          />
    
          <Button
            onPress={formik.handleSubmit}
            isDisabled={!formik.isValid || formik.isSubmitting}
            isLoading={formik.isSubmitting}
          >
            Login
          </Button>
        </VStack>
      );
    };
    ```
    
    ## 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
    <Input label="Email" {...getFieldProps('email')} />
    ```
    
    ### Select/Picker Helper
    
    ```tsx
    <Select
      label="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
    
    ```tsx
    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 */}
          <Button
            onPress={formik.handleSubmit}
            isDisabled={!formik.isValid || formik.isSubmitting}
            isLoading={formik.isSubmitting}
          >
            Create
          </Button>
        </VStack>
      );
    };
    ```
    
    ## Edit Form with Initial Values
    
    ```tsx
    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 (
        <VStack gap="$4">
          {/* Form fields */}
          <Button
            onPress={formik.handleSubmit}
            isDisabled={!hasChanges || !formik.isValid || formik.isSubmitting}
            isLoading={formik.isSubmitting}
          >
            Save Changes
          </Button>
        </VStack>
      );
    };
    ```
    
    ## Form State Helpers
    
    ```tsx
    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
    
    ```tsx
    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 (
        <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>
            )}
            <Button
              onPress={formik.handleSubmit}
              isDisabled={!formik.isValid}
              isLoading={formik.isSubmitting}
            >
              {step < steps.length - 1 ? 'Next' : 'Submit'}
            </Button>
          </HStack>
        </VStack>
      );
    };
    ```
    
    ## Anti-Patterns
    
    ```tsx
    // WRONG - Not showing validation errors
    <Input
      value={formik.values.email}
      onChangeText={formik.handleChange('email')}
    />
    
    // CORRECT - Show errors when touched
    <Input
      value={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
    <Button
      onPress={formik.handleSubmit}
      isDisabled={!formik.isValid || formik.isSubmitting}
      isLoading={formik.isSubmitting}
    >
      Submit
    </Button>
    
    
    // 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
    
    ## 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.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related