π₯ Stop Writing Boilerplate! How Formik + React Hooks Can Save You 500 Hours This Year π₯
Introduction
Let's face it β form handling in React can be painful. As projects grow, developers often find themselves buried under piles of form validations, state management, and inconsistent user experiences.
You mightβve already heard of Formik β the popular form library for React. But what if I told you that most developers are underusing it? In this post, weβre going to dive into a powerful, lesser-known combination: Formik + Custom React Hooks.
We'll explore how this pairing can:
- Eliminate repetitive form boilerplate code π―
- Make your form components cleaner and more maintainable π
- Improve validation UX without breaking your brain π§
Ready to save weeks editing form logic in 2024? Let's get our hands dirty. π οΈ
π£ The Pain: Forms Before Formik (and Why Hooks Alone Arenβt Enough)
A typical login form in plain React might look like this:
// LoginForm.jsx
import { useState } from 'react';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(null);
const handleSubmit = e => {
e.preventDefault();
if (!email || !password) {
setError('All fields are required.');
return;
}
// Call API or authenticate...
};
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
{error && <p>{error}</p>}
<button type="submit">Login</button>
</form>
);
}
export default LoginForm;
Not terrible, but this is just login. Doing even basic validation across five different forms? Youβre duplicating logic like a robot.
π Enter Formik + Yup: The Validation Dream Team
Formik simplifies form state and validation management. Pairing it with Yup, a JS schema validator, adds declarative, composable validation.
Letβs rewrite our login form the Formik way:
// LoginFormWithFormik.jsx
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'),
});
function LoginForm() {
const handleSubmit = (values) => {
console.log('Submitted:', values);
// Call API
};
return (
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={LoginSchema}
onSubmit={handleSubmit}
>
<Form>
<Field name="email" type="email" />
<ErrorMessage name="email" component="div" />
<Field name="password" type="password" />
<ErrorMessage name="password" component="div" />
<button type="submit">Login</button>
</Form>
</Formik>
);
}
export default LoginForm;
β
Zero manual useState management
β
Disables submit on validation errors
β
Clean, declarative code
π§ Superpower Combo: Custom Hooks + Formik Context
What if you need to reuse business logic across forms? Create a custom hook to encapsulate logic!
Letβs Make a useLoginForm Hook
// hooks/useLoginForm.js
import * as Yup from 'yup';
export const useLoginForm = () => {
const initialValues = { email: '', password: '' };
const validationSchema = Yup.object({
email: Yup.string().email('Bad email').required('Required'),
password: Yup.string().min(6, 'Minimum 6 characters').required('Required'),
});
const onSubmit = (values, { setSubmitting }) => {
console.log('Login:', values);
// ...API call
setSubmitting(false);
};
return { initialValues, validationSchema, onSubmit };
};
Now update your form to use this hook:
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { useLoginForm } from './hooks/useLoginForm';
function LoginForm() {
const form = useLoginForm();
return (
<Formik {...form}>
<Form>
<Field name="email" type="email" />
<ErrorMessage name="email" component="div" />
<Field name="password" type="password" />
<ErrorMessage name="password" component="div" />
<button type="submit">Login</button>
</Form>
</Formik>
);
}
π§Ό Clean, donβt repeat yourself (DRY), fully reusable.
You could easily turn useLoginForm into useFormBuilder(type) and build a dynamic, unified form system.
π Bonus: Real-Time UX Improvements with useField Hook
Formik also lets you access field state via hooks:
import { useField } from 'formik';
function FancyEmailInput() {
const [field, meta] = useField('email');
return (
<div>
<input {...field} type="email" placeholder="What's your email?" />
{meta.touched && meta.error && (
<span className="error">{meta.error}</span>
)}
</div>
);
}
Use this pattern to build entire design systems that interface seamlessly with Formik.
β¨ Takeaways: Use these patterns today
Few things can instantly 10x DX (Developer Experience) like having:
- A consistent form logic template via custom hooks
- Declarative validation thatβs testable
- Components that separate UX and logic cleanly
In teams, this pays off instantly by reducing onboarding time, PR reviews, and logic bugs inside forms.
π§ͺ TLDR β Code Snippets Crash Course
Hereβs what we covered:
β
Plain Form β Boilerplate
β
Formik + Yup β Cleaner, validatable code
β
Custom Hooks + Formik β Scalable, reusable logic
β
useField β Power up custom components
π‘ Pro Tip: Add Formik Debug Tools
<pre>{JSON.stringify(values, null, 2)}</pre>
<pre>{JSON.stringify(errors, null, 2)}</pre>
Add them below forms in dev mode for ultra-fast debugging.
π§ Where to Learn More
π Final Word
Forms donβt have to be painful. With the Formik + React Hooks combo, you'll write less code, have fewer bugs, and stop pulling your hair out.
Start using these patterns today and you just might earn back ~500 hours this year β or at least your front-end sanity π.
π¬ Have a juicy form horror story? Share it in the comments below!
π οΈ Tags: react, formik, web-development, productivity
β¨ If you need this done β we offer frontend development services!
Top comments (2)
Quick clarification: you mention βDisables submit on validation errors,β but the Formik example doesn't disable the button. Are you using isValid/isSubmitting to set disabled on the submit, or is there another pattern you recommend? A small snippet would help.
Good catch β youβre right, the example doesnβt disable submit out of the box. The usual pattern is to wire up isValid and isSubmitting from Formikβs render props or hooks. For example:
That way the button stays disabled until the form is valid and not currently submitting.