Skip to main content

formik-patterns

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

소스 정보

저장소
ChrisWiles/claude-code-showcase
최근 소스 활동
2026년 1월 6일 18:50
감지된 SKILL.md 언어
영어
스타
6,070
포크
576

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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 ( <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
GitHub에서 보기