π₯ 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:
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
.
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.
Letβs rebuild the above form using Formik and Yup.
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. π
useState
pings for every input fieldFormik 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.
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; })
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.
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 β¨.
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.
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:
So go ahead β refactor that wild west form you've been avoiding. Your future self will thank you.
Happy form-building! πͺ
πΌ If you need help building scalable frontends like this β we offer such services: https://ekwoster.dev/service/frontend-development
Information