DEV Community

Cover image for Why Your React Forms Keep Breaking — And How Formik & Yup Can Save Your Sanity
Yevhen Kozachenko 🇺🇦
Yevhen Kozachenko 🇺🇦

Posted on • Originally published at ekwoster.dev

Why Your React Forms Keep Breaking — And How Formik & Yup Can Save Your Sanity

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:

  1. Handle dynamic nested fields (like arrays of objects).
  2. Do conditional validation.
  3. Integrate file uploads into forms.
  4. Build forms that work, even under stress.

Let’s build something real – A job application form with multiple experiences, file uploads (résumé), and conditional validation.


📦 The Stack

  • React (v18+)
  • Formik (v3.1.1)
  • Yup (v1.2.0)
  • React Dropzone (for file upload integration)

First, install the dependencies:

npm install formik yup react-dropzone
Enter fullscreen mode Exit fullscreen mode

🧠 The Use Case: Dynamic Job Application Form

Our job application form will contain:

  • Personal Information
  • Multiple job experiences (Company, Role, Years)
  • A résumé upload
  • Conditional required fields (e.g., LinkedIn is required if the candidate has more than 3 years of experience)

Let’s build it.


🚀 Step-by-Step Formik with Yup in Real World Scenario

Base Setup

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'),
});
Enter fullscreen mode Exit fullscreen mode

File Upload with React Dropzone

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>
  );
};
Enter fullscreen mode Exit fullscreen mode

Full Form in JSX

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

🧼 Why This Matters

This setup demonstrates how Formik + Yup can tackle real-world form problems:

  • 🔁 Repeating and nested fields (e.g., work experience).
  • 🧩 Conditional logic with Yup (e.g., require LinkedIn based on total years).
  • 📎 File uploads with minimal headache.
  • 🧪 Strong validation, even with edge cases.

Without Formik and Yup, managing this kind of complexity would involve a lot of manual state, useEffects, and homegrown validation logic. Which breaks.


🔍 Pro Tips

  • Always create reusable components for things like File inputs that need special hooks.
  • Use Yup.test() when you need to reference sibling fields.
  • For massive forms, consider separators, wizards, or step validators.

📚 Resources


🧩 Wrapping Up

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.

Top comments (0)