Why Your React Forms Keep Breaking — And How Formik & Yup Can Save Your Sanity
Form management in React is deceptively simple... until it's not. From validation hell to nested form states, uncontrolled inputs, and the dreaded "Cannot read property of undefined" errors, React forms can quickly become unmaintainable beasts.
But fear not! In this post, we're diving deep into Formik — a powerful and popular React form library — coupled with Yup for validation, to demonstrate how you can create scalable, clean, and robust forms without losing your mind.
We're not going to do another “Here’s a login form” tutorial. Instead, you'll see how to:
Let’s build something real – A job application form with multiple experiences, file uploads (résumé), and conditional validation.
First, install the dependencies:
npm install formik yup react-dropzone
Our job application form will contain:
Let’s build it.
import { Formik, Form, Field, FieldArray, ErrorMessage } from 'formik'; import * as Yup from 'yup'; import { useDropzone } from 'react-dropzone'; const initialValues = { name: '', email: '', linkedin: '', experiences: [ { company: '', role: '', years: '' }, ], resume: null, }; const validationSchema = Yup.object({ name: Yup.string().required('Name is required'), email: Yup.string().email('Invalid email').required('Email is required'), linkedin: Yup.string().when('experiences', (experiences, schema) => { const totalYears = experiences.reduce((acc, curr) => acc + Number(curr.years || 0), 0); return totalYears > 3 ? schema.required('LinkedIn is required for senior roles') : schema; }), experiences: Yup.array().of( Yup.object({ company: Yup.string().required('Company is required'), role: Yup.string().required('Role is required'), years: Yup.number().min(0, 'Must be positive').required('Years is required'), }) ), resume: Yup.mixed().required('Resume is required'), });
Create a reusable dropzone with Formik support:
const FileUpload = ({ field, form }) => { const { getRootProps, getInputProps } = useDropzone({ onDrop: acceptedFiles => { form.setFieldValue(field.name, acceptedFiles[0]); }, }); return ( <div {...getRootProps()} className="dropzone"> <input {...getInputProps()} /> {field.value ? ( <p>{field.value.name}</p> ) : ( <p>Drag 'n' drop your résumé, or click to select file</p> )} </div> ); };
export default function ApplicationForm() { const handleSubmit = async (values) => { const formData = new FormData(); for (const key in values) { if (key === 'experiences') { formData.append('experiences', JSON.stringify(values[key])); } else if (key === 'resume') { formData.append('resume', values[key]); } else { formData.append(key, values[key]); } } // send formData to server ➡️ console.log([...formData.entries()]); }; return ( <Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={handleSubmit} > {({ values }) => ( <Form> <label>Name</label> <Field name="name" /> <ErrorMessage name="name" component="div" /> <label>Email</label> <Field name="email" type="email" /> <ErrorMessage name="email" component="div" /> <label>LinkedIn</label> <Field name="linkedin" /> <ErrorMessage name="linkedin" component="div" /> <FieldArray name="experiences"> {({ push, remove }) => ( <div> {values.experiences.map((_, i) => ( <div key={i}> <label>Company</label> <Field name={`experiences[${i}].company`} /> <ErrorMessage name={`experiences[${i}].company`} component="div" /> <label>Role</label> <Field name={`experiences[${i}].role`} /> <ErrorMessage name={`experiences[${i}].role`} component="div" /> <label>Years</label> <Field name={`experiences[${i}].years`} type="number" /> <ErrorMessage name={`experiences[${i}].years`} component="div" /> {i > 0 && <button type="button" onClick={() => remove(i)}>Remove</button>} </div> ))} <button type="button" onClick={() => push({ company: '', role: '', years: '' })}>Add Experience</button> </div> )} </FieldArray> <label>Upload Résumé</label> <Field name="resume" component={FileUpload} /> <ErrorMessage name="resume" component="div" /> <button type="submit">Submit</button> </Form> )} </Formik> ); }
This setup demonstrates how Formik + Yup can tackle real-world form problems:
Without Formik and Yup, managing this kind of complexity would involve a lot of manual state, useEffects, and homegrown validation logic. Which breaks.
React forms can be a joy… when you stop trying to do everything manually. Formik and Yup are the power tools you didn't know you needed — until the form gets real.
Next time you're building a complex form, stop reinventing the wheel. Reach for Formik and let your forms thrive.
✌️ Happy Forming!
⭐️ If you need this done – we offer frontend development services to help you ship polished React apps with solid form architecture.
Information