π Stop Using Forms Like It's 2015: Master Complex Forms in React with Formik + Yup + Dynamic Fields
If you've ever built a form in React, chances are you've ended up tangled in useState() hooks, onChange handlers, and spaghetti validation logic that makes your app feel more like a bomb squad operation than front-end development.
β Let's face it: Forms in React suck... unless you're using the right tools.
Say hello to your new best friends:
- β Formik β Dead-simple form state management.
- β Yup β Robust, schema-based validations.
- β Dynamic Fields β Because your forms aren't static, and your logic shouldn't be either.
In this blog post, you're going to level-up from basic form handling to building complex, conditional, dynamic, and validated forms like a true pro.
π¦ Setting the Stage: Basic Setup
First, install the necessary packages:
npm install formik yup
Create a Simple Form
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const SignupSchema = Yup.object().shape({
name: Yup.string().required('Required'),
email: Yup.string().email('Invalid email').required('Required'),
password: Yup.string().min(6, 'Too Short!').required('Required')
});
const SimpleForm = () => (
<div>
<h1>Signup</h1>
<Formik
initialValues={{ name: '', email: '', password: '' }}
validationSchema={SignupSchema}
onSubmit={values => {
console.log(values);
}}
>
{() => (
<Form>
<label>Name</label>
<Field name="name" />
<ErrorMessage name="name" component="div" />
<label>Email</label>
<Field name="email" type="email" />
<ErrorMessage name="email" component="div" />
<label>Password</label>
<Field name="password" type="password" />
<ErrorMessage name="password" component="div" />
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
export default SimpleForm;
π‘ Going Dynamic: Adding Fields on the Fly
Letβs say you want users to input multiple hobbies, dynamically adding fields. You donβt want to guess how many fields they need, right? You want something smart.
Enter FieldArray βΉοΈββοΈ
Example: Add/Remove Dynamic Fields
import { FieldArray } from 'formik';
const DynamicForm = () => (
<Formik
initialValues={{ hobbies: [''] }}
onSubmit={values => {
console.log(values);
}}
>
{({ values }) => (
<Form>
<h1>Enter Hobbies</h1>
<FieldArray name="hobbies">
{({ push, remove }) => (
<div>
{values.hobbies.map((_, index) => (
<div key={index}>
<Field name={`hobbies.${index}`} />
<button type="button" onClick={() => remove(index)}>
Remove
</button>
</div>
))}
<button type="button" onClick={() => push('')}>
Add Hobby
</button>
</div>
)}
</FieldArray>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);
π§ Smart Forms: Conditional Rendering
Serious forms require conditional logic. For example, if a user selects "Employed", show employer-related fields.
Example: Employment Conditional Fields
const ConditionalForm = () => (
<Formik
initialValues={{ status: '', company: '' }}
validationSchema={Yup.object({
status: Yup.string().required(),
company: Yup.string().when('status', {
is: 'employed',
then: Yup.string().required('Company name is required')
})
})}
onSubmit={values => console.log(values)}
>
{({ values }) => (
<Form>
<label>Status</label>
<Field as="select" name="status">
<option value="">Select</option>
<option value="student">Student</option>
<option value="employed">Employed</option>
</Field>
<ErrorMessage name="status" component="div" />
{values.status === 'employed' && (
<>
<label>Company</label>
<Field name="company" />
<ErrorMessage name="company" component="div" />
</>
)}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
);
β Now youβve got schema-based validation AND dynamic fields β like a boss.
π Bonus: Reusable Input Components
Donβt repeat yourself. Abstract form inputs like this:
const MyTextInput = ({ label, ...props }) => (
<div>
<label>{label}</label>
<Field {...props} />
<ErrorMessage name={props.name} component="div" />
</div>
);
// Use it like:
<MyTextInput label="Name" name="name" type="text" />
π§ͺ Testing Your Forms
Formik forms return predictable props that are very testable using Testing Library:
import { render, screen, fireEvent } from '@testing-library/react';
import SimpleForm from './SimpleForm';
test('form submits correct data', () => {
render(<SimpleForm />);
fireEvent.change(screen.getByLabelText(/name/i), { target: { value: 'Alice' } });
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'alice@example.com' } });
fireEvent.change(screen.getByLabelText(/password/i), { target: { value: '123456' } });
fireEvent.click(screen.getByText(/submit/i));
// Add your expectations or mock submit handler here
});
π€― Final Thoughts
Formik + Yup isnβt just a pairing, itβs a superpower combo for form management in React. Youβve seen how to:
- Manage small or complex forms effortlessly
- Add/remove fields dynamically
- Validate fields conditionally
- Create comprehensive reusable components
Youβre now ready to build enterprise-grade forms without losing hair. π§ββοΈβ¨
π Next-Level Challenge
Want to go further? Try integrating:
- Autosave feature on field change.
- Async validation (e.g., check email availability).
- Multi-step wizards with Formik context.
Or... try building a form builder using Formik + JSON schemas π€―
π§΅ TL;DR:
- Use Formik for sanity π―
- Use Yup for clarity π
- Go dynamic for reality π
Start building forms that work for users β not against them.
Happy Coding! π
π‘ If you need help building dynamic, interactive frontends like this - we offer frontend development services.
Top comments (0)