๐ฅ 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.