π₯ 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