Formik is a popular form library for React that helps with form state management, validation, and submission handling. Here’s everything you need to know to use Formik effectively:
Core Concepts
1. Basic Formik Setup
import { Formik, Form, Field, ErrorMessage } from 'formik';
function BasicForm() {
return (
<Formik
initialValues={{ email: '', password: '' }}
onSubmit={(values, { setSubmitting }) => {
console.log(values);
setSubmitting(false);
}}
>
{({ isSubmitting }) => (
<Form>
<Field type="email" name="email" />
<ErrorMessage name="email" component="div" />
<Field type="password" name="password" />
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
</Formik>
);
}
2. Validation with Yup
import * as Yup from 'yup';
const validationSchema = Yup.object().shape({
email: Yup.string()
.email('Invalid email')
.required('Required'),
password: Yup.string()
.min(8, 'Too short!')
.required('Required')
});
function ValidationForm() {
return (
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={validationSchema}
onSubmit={(values) => console.log(values)}
>
{({ isSubmitting }) => (
<Form>
<Field type="email" name="email" placeholder="Email" />
<ErrorMessage name="email" component="div" className="error" />
<Field type="password" name="password" placeholder="Password" />
<ErrorMessage name="password" component="div" className="error" />
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
</Formik>
);
}
Advanced Features
1. Handling Form State
function StatefulForm() {
return (
<Formik
initialValues={{ username: '', rememberMe: false }}
onSubmit={(values) => console.log(values)}
>
{({ values, handleChange, handleBlur }) => (
<Form>
<Field
name="username"
onChange={handleChange}
onBlur={handleBlur}
value={values.username}
/>
<label>
<Field type="checkbox" name="rememberMe" />
Remember me
</label>
<pre>{JSON.stringify(values, null, 2)}</pre>
</Form>
)}
</Formik>
);
}
2. Dynamic Fields
function DynamicForm() {
return (
<Formik
initialValues={{ friends: [''] }}
onSubmit={(values) => console.log(values)}
>
{({ values }) => (
<Form>
{values.friends.map((friend, index) => (
<div key={index}>
<Field name={`friends.${index}`} />
<button
type="button"
onClick={() => arrayHelpers.remove(index)}
>
Remove
</button>
</div>
))}
<button
type="button"
onClick={() => arrayHelpers.push('')}
>
Add Friend
</button>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);
}
3. Custom Field Components
function CustomInput({ field, form, ...props }) {
return (
<div>
<input {...field} {...props} />
{form.touched[field.name] && form.errors[field.name] && (
<div className="error">{form.errors[field.name]}</div>
)}
</div>
);
}
function CustomFieldForm() {
return (
<Formik
initialValues={{ email: '' }}
onSubmit={(values) => console.log(values)}
>
<Form>
<Field name="email" component={CustomInput} placeholder="Email" />
<button type="submit">Submit</button>
</Form>
</Formik>
);
}
Integration Patterns
1. With UI Libraries (Material-UI Example)
import { TextField, Checkbox, FormControlLabel } from '@material-ui/core';
function MuiForm() {
return (
<Formik
initialValues={{ name: '', agree: false }}
onSubmit={(values) => console.log(values)}
>
{({ values, handleChange, handleBlur }) => (
<Form>
<TextField
name="name"
label="Name"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
fullWidth
/>
<FormControlLabel
control={
<Checkbox
name="agree"
checked={values.agree}
onChange={handleChange}
/>
}
label="I agree to terms"
/>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);
}
2. With React Context
const FormContext = React.createContext();
function FormProvider({ children }) {
const formik = useFormik({
initialValues: { theme: 'light' },
onSubmit: values => console.log(values)
});
return (
<FormContext.Provider value={formik}>
<Form>{children}</Form>
</FormContext.Provider>
);
}
function ThemedForm() {
const { values, handleChange } = React.useContext(FormContext);
return (
<>
<select
name="theme"
value={values.theme}
onChange={handleChange}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</>
);
}
Best Practices
- Initial Values: Always provide complete initial values
initialValues={{
username: '',
email: '',
address: {
street: '',
city: ''
}
}}
- Validation Strategies:
- Use Yup for complex validation
- For simple validation, use
validate
function:jsx validate={values => { const errors = {}; if (!values.email) errors.email = 'Required'; return errors; }}
- Form Submission:
onSubmit={(values, { setSubmitting, resetForm }) => {
submitToAPI(values)
.then(() => {
setSubmitting(false);
resetForm();
})
.catch(error => {
setSubmitting(false);
setErrors({ form: error.message });
});
}}
- Performance Optimization:
- Use
React.memo
for custom field components - Split large forms into smaller components
- Use
shouldValidate
andshouldReset
props carefully
- Accessibility:
- Always associate labels with inputs
- Provide proper error messages
- Use ARIA attributes where appropriate
Comparison with React Hook Form
While both libraries solve similar problems, here’s when to choose Formik:
- Choose Formik when:
- You prefer a more declarative API
- You need built-in form state management
- You’re already using Yup for validation
- You work with complex nested forms
- Choose React Hook Form when:
- Performance is critical (fewer re-renders)
- You prefer uncontrolled components
- You need minimal bundle size
- You work with very large forms
Formik provides excellent developer experience and remains a great choice for many React applications, especially those that benefit from its structured approach to form management.