Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Formik form handling with validation patterns. Use when building forms, implementing validation, or handling form submission.
.claude/skills/sickn33-formik-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 75% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 12% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 112% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 102% | 0% |
Use this skill when you need formik form handling with validation patterns. Use when building forms, implementing validation, or handling form submission.
tsximport { 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> ); };
typescriptimport * 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')
typescriptconst schema = yup.object({ hasCompany: yup.boolean(), companyName: yup.string().when('hasCompany', { is: true, then: (schema) => schema.required('Company name required'), otherwise: (schema) => schema.nullable(), }), });
tsxconst 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')} />
tsx<Select label="Country" value={formik.values.country} onValueChange={(value) => formik.setFieldValue('country', value)} error={formik.touched.country ? formik.errors.country : undefined} options={countryOptions} />
tsxconst 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> ); };
tsxconst 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> ); };
tsxconst { 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;
tsxconst 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> ); };
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); } }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 12,909 | 10,102 | -22% | 1 | 1 | 0% | 2,675 | 4,681 | +75% | 0 | 0 | — |
case-02 | fail→pass | 24,083 | 19,709 | -18% | 1 | 1 | 0% | 5,034 | 6,823 | +36% | 0 | 0 | — |
case-03 | fail→pass | 16,826 | 6,891 | -59% | 1 | 1 | 0% | 3,691 | 4,134 | +12% | 0 | 0 | — |
case-04 | pass→pass | 18,498 | 13,591 | -27% | 1 | 1 | 0% | 3,451 | 5,313 | +54% | 0 | 0 | — |
case-05 | pass→fail | 24,428 | 16,745 | -31% | 1 | 1 | 0% | 4,275 | 5,513 | +29% | 0 | 0 | — |
case-06 | pass→pass | 14,107 | 9,590 | -32% | 1 | 1 | 0% | 2,741 | 4,471 | +63% | 0 | 0 | — |
case-07 | fail→pass | 7,815 | 3,525 | -55% | 1 | 1 | 0% | 1,464 | 3,107 | +112% | 0 | 0 | — |
case-08 | pass→pass | 6,239 | 4,055 | -35% | 1 | 1 | 0% | 906 | 3,239 | +258% | 0 | 0 | — |
case-09 | fail→pass | 8,919 | 4,119 | -54% | 1 | 1 | 0% | 1,640 | 3,309 | +102% | 0 | 0 | — |
case-10 | fail→pass | 12,570 | 8,992 | -28% | 1 | 1 | 0% | 2,254 | 4,304 | +91% | 0 | 0 | — |
case-11 | fail→pass | 14,158 | 10,955 | -23% | 1 | 1 | 0% | 2,637 | 4,264 | +62% | 0 | 0 | — |
case-12 | pass→pass | 13,500 | 9,811 | -27% | 1 | 1 | 0% | 2,712 | 4,619 | +70% | 0 | 0 | — |
case-13 | pass→pass | 12,687 | 13,201 | +4% | 1 | 1 | 0% | 2,231 | 4,192 | +88% | 0 | 0 | — |
case-14 | pass→pass | 13,347 | 10,349 | -22% | 1 | 1 | 0% | 2,556 | 4,405 | +72% | 0 | 0 | — |
case-15 | pass→pass | 7,599 | 5,443 | -28% | 1 | 1 | 0% | 1,279 | 3,385 | +165% | 0 | 0 | — |
case-16 | pass→pass | 4,580 | 3,074 | -33% | 1 | 1 | 0% | 732 | 3,128 | +327% | 0 | 0 | — |
case-17 | pass→pass | 4,075 | 3,530 | -13% | 1 | 1 | 0% | 763 | 2,966 | +289% | 0 | 0 | — |
case-18 | pass→pass | 11,767 | 3,758 | -68% | 1 | 1 | 0% | 2,140 | 3,185 | +49% | 0 | 0 | — |
case-19 | fail→fail | 15,180 | 11,561 | -24% | 1 | 1 | 0% | 2,938 | 4,744 | +61% | 0 | 0 | — |
case-20 | pass→pass | 17,068 | 7,587 | -56% | 1 | 1 | 0% | 3,411 | 3,842 | +13% | 0 | 0 | — |
case-21 | fail→pass | 10,790 | 7,285 | -32% | 1 | 1 | 0% | 1,898 | 3,834 | +102% | 0 | 0 | — |
case-22 | pass→pass | 12,984 | 8,560 | -34% | 1 | 1 | 0% | 2,509 | 4,211 | +68% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.