Written by: ekwoster.dev on Mon Aug 25

πŸ”₯ Say Goodbye to bloated React Forms Forever: How Formik + Yup Can Save You Hours of Debugging

πŸ”₯ Say Goodbye to bloated React Forms Forever: How Formik + Yup Can Save You Hours of Debugging

Cover image for πŸ”₯ Say Goodbye to bloated React Forms Forever: How Formik + Yup Can Save You Hours of Debugging

πŸ”₯ Say Goodbye to bloated React Forms Forever: How Formik + Yup Can Save You Hours of Debugging

If you've ever built dynamic forms in React, chances are you've faced one or more of these:

  • State chaos ("Wait, which field's state is triggering this bug?")
  • Unreliable validation logic across countless fields
  • Boilerplate code just to handle a few inputs
  • Difficulty maintaining dozens of similar forms across your app

If that sounds familiar, you're not alone. Forms in React can get ridiculously complicated, especially when validation and reusable components enter the scene. But there's a better way.

Let me introduce you to a power combo that will simplify your forms, save you time, and reduce your blood pressure: Formik + Yup.


βœ‹ The Problem: React Forms Are a Trap

A common approach looks like this:

const MyForm = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState({});

  const handleSubmit = (e) => {
    e.preventDefault();

    if (!email.includes('@')) {
      setErrors(prev => ({...prev, email: 'Invalid email'}));
      return;
    }

    if (password.length < 6) {
      setErrors(prev => ({...prev, password: 'Password too short'}));
      return;
    }

    // submit logic here
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={e => setEmail(e.target.value)} />
      {errors.email && <div>{errors.email}</div>}

      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      {errors.password && <div>{errors.password}</div>}

      <button type="submit">Submit</button>
    </form>
  );
};

This works... but is it scalable? Not even a little. Wait until you have 15 fields and multiple form variants.


πŸ§ͺ Enter Formik & Yup: The Dynamic Duo

  • Formik handles the form state, submission, and interaction.
  • Yup handles schema-based validation. Think of it like Yup = Joi (from Node) but made for browsers.

Let’s rebuild the above form using Formik and Yup.


πŸš€ From 40+ lines to 20: The Refactor

Install the packages:

npm install formik yup

Then, here’s the beauty:

import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';

const LoginSchema = Yup.object().shape({
  email: Yup.string().email('Invalid email').required('Required'),
  password: Yup.string().min(6, 'Too short').required('Required'),
});

const MyForm = () => {
  return (
    <Formik
      initialValues={{ email: '', password: '' }}
      validationSchema={LoginSchema}
      onSubmit={(values) => {
        console.log('Form submitted:', values);
      }}
    >
      {() => (
        <Form>
          <div>
            <Field name="email" type="email" />
            <ErrorMessage name="email" component="div" />
          </div>

          <div>
            <Field name="password" type="password" />
            <ErrorMessage name="password" component="div" />
          </div>

          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  );
};

Boom. πŸš€

  • No more useState pings for every input field
  • Built-in validation
  • Cleaner code you can scale

πŸ› οΈ Dynamic Forms Done Right

Formik shines even more when you create dynamic fields.

Say you want a survey where users can add more questions:

import { FieldArray } from 'formik';

<Formik
  initialValues={{ questions: [''] }}
  onSubmit={...}
>
  {({ values }) => (
    <Form>
      <FieldArray name="questions">
        {({ push, remove }) => (
          <div>
            {values.questions.map((q, index) => (
              <div key={index}>
                <Field name={`questions.${index}`} />
                <button type="button" onClick={() => remove(index)}>-</button>
              </div>
            ))}
            <button type="button" onClick={() => push('')}>Add</button>
          </div>
        )}
      </FieldArray>

      <button type="submit">Submit</button>
    </Form>
  )}
</Formik>

No more building arrays via useState or fighting controlled inputs.


πŸ”’ Real-World Powered Validation with Yup

Yup can validate just about anything:

const schema = Yup.object().shape({
  username: Yup.string().matches(/^[a-z0-9_]+$/, 'Alphanumerics and underscores only'),
  age: Yup.number().min(18, 'Must be 18+'),
  email: Yup.string().email(),
  tags: Yup.array().of(Yup.string().required()),
});

You can even write async validation like checking if a username is taken:

username: Yup.string()
  .required()
  .test('checkUsernameExists', 'Username already taken', async (value) => {
    const res = await fetch(`/api/check-username?value=${value}`);
    const { available } = await res.json();
    return available;
  })

πŸ§ͺ Test It Like A Boss

Formik outputs errors in a predictable format, which helps with snapshot and unit testing in your components. No more weird, deeply nested states that testing libraries choke on.


🎨 Bonus: Styling with Tailwind (or CSS-in-JS)

This is 100% tailwind-friendly:

<Field name="email" className="border p-2 rounded w-full" />
<ErrorMessage name="email" component="div" className="text-red-500 mt-1" />

Or use your favorite styled-components setup. The point is β€” it stays ✨ clean ✨.


🧡 When NOT to Use Formik

Although Formik is fantastic, if you're building tiny forms (1-2 inputs), or your app is already tightly coupled using other techniques (like Redux-form), you might not need it β€” adding it in can feel like overkill.


🧠 Final Thoughts: Scale Without Pain

Formik + Yup isn't just a convenience β€” it's architectural sanity. By separating validation logic from component logic, and state from submission handlers, your forms become:

  • Easier to reason about
  • Cleaner to test
  • Faster to replicate for new features

So go ahead β€” refactor that wild west form you've been avoiding. Your future self will thank you.


🧭 Resources

Happy form-building! πŸ’ͺ

πŸ’Ό If you need help building scalable frontends like this – we offer such services: https://ekwoster.dev/service/frontend-development