Introduction
Ever felt stuck managing forms in React—especially when they need to change based on user actions or data? You’re not alone. Static forms are easy, but real-world applications demand dynamic behavior. Whether you’re building a survey, onboarding workflow, or a dashboard setting page, dynamic forms with real-time validation are a must.
So here’s the challenge: can you do it without form libraries like Formik or React Hook Form? Yes—you absolutely can. And in this article, I’ll show you how to use forms in React like a pro, with clean code, dynamic validation, and a simple yet powerful architecture.
Let’s dive in.
Why Use Dynamic Forms in React?
Dynamic forms allow you to change form fields at runtime—based on user choices, backend data, or logic. This makes your UI adaptable and powerful.
Benefits of Dynamic Forms
- Flexible UI – Show/hide inputs based on conditions.
- Reusable Components – Render form elements from a config.
- Scalability – Add fields programmatically (think onboarding flows or custom settings).
- Improved UX – Prevent clutter and guide users step by step.
Example use case: In a job application form, you might show extra fields like “Portfolio URL” only if the selected position is “Designer.”
Setting Up: Tools You Need
We won’t use form libraries—but we can use a schema validator like Yup for robust validations.
Install Yup:
npm install yup
That’s it. Everything else will be good old React + useState + useEffect.
Create a Dynamic Form Component
Let’s build this step-by-step.
Step 1: Define a Form Schema
const formSchema = [
{
name: 'fullName',
label: 'Full Name',
type: 'text',
validations: [
{ validator: 'required', err_message: 'Full Name is required' },
{ validator: 'min', value: 3, err_message: 'Minimum 3 characters' }
]
},
{
name: 'email',
label: 'Email Address',
type: 'email',
validations: [
{ validator: 'required', err_message: 'Email is required' },
{ validator: 'email', err_message: 'Invalid email format' }
]
},
{
name: 'role',
label: 'Role',
type: 'select',
options: ['Developer', 'Designer'],
validations: [{ validator: 'required', err_message: 'Role is required' }]
},
{
name: 'portfolio',
label: 'Portfolio URL',
type: 'text',
requiredIf: { field: 'role', value: 'Designer' },
validations: [
{ validator: 'required', err_message: 'Portfolio URL is required for designers' },
{ validator: 'url', err_message: 'Must be a valid URL' }
]
}
];
✅ How to Convert This Schema to a Yup Validation Object
You can dynamically build the Yup schema like this:
import * as Yup from 'yup';
const buildValidationSchema = (formData) => {
const shape = {};
formSchema.forEach(field => {
let validator = Yup.string();
if (field.type === 'email') validator = Yup.string().email();
// Handle requiredIf condition
const isConditionallyRequired = field.requiredIf &&
formData[field.requiredIf.field] === field.requiredIf.value;
if (field.validations) {
field.validations.forEach(rule => {
switch (rule.validator) {
case 'required':
if (!field.requiredIf || isConditionallyRequired) {
validator = validator.required(rule.err_message);
}
break;
case 'email':
validator = validator.email(rule.err_message);
break;
case 'min':
validator = validator.min(rule.value, rule.err_message);
break;
case 'max':
validator = validator.max(rule.value, rule.err_message);
break;
case 'url':
validator = validator.url(rule.err_message);
break;
// Add more custom validators as needed
}
});
}
shape[field.name] = validator;
});
return Yup.object().shape(shape);
};
🧠 Pro Tip: Use this array to render fields dynamically. You can store it in a DB and load it via API too.
Step 2: Manage Form State
const [formData, setFormData] = useState({});
const [errors, setErrors] = useState({});
Initialize fields based on formSchema
.
useEffect(() => {
const initialData = {};
formSchema.forEach(field => {
initialData[field.name] = '';
});
setFormData(initialData);
}, []);
Step 3: Render the Form Dynamically
<form onSubmit={handleSubmit}>
{formSchema.map(field => {
if (field.requiredIf) {
const conditionMet = formData[field.requiredIf.field] === field.requiredIf.value;
if (!conditionMet) return null;
}
return (
<div key={field.name}>
<label>{field.label}</label>
{field.type === 'select' ? (
<select
name={field.name}
value={formData[field.name]}
onChange={handleChange}
>
<option value="">Select</option>
{field.options.map(option => (
<option key={option} value={option}>{option}</option>
))}
</select>
) : (
<input
type={field.type}
name={field.name}
value={formData[field.name]}
onChange={handleChange}
/>
)}
{errors[field.name] && <span style={{ color: 'red' }}>{errors[field.name]}</span>}
</div>
);
})}
<button type="submit">Submit</button>
</form>
Step 4: Handle Input Change
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
Validating the Form with Yup
Let’s dynamically generate a Yup schema based on formSchema
.
import * as Yup from 'yup';
const buildValidationSchema = (formData) => {
const shape = {};
formSchema.forEach(field => {
let validator = Yup.string();
if (field.type === 'email') validator = Yup.string().email();
// Handle requiredIf condition
const isConditionallyRequired = field.requiredIf &&
formData[field.requiredIf.field] === field.requiredIf.value;
if (field.validations) {
field.validations.forEach(rule => {
switch (rule.validator) {
case 'required':
if (!field.requiredIf || isConditionallyRequired) {
validator = validator.required(rule.err_message);
}
break;
case 'email':
validator = validator.email(rule.err_message);
break;
case 'min':
validator = validator.min(rule.value, rule.err_message);
break;
case 'max':
validator = validator.max(rule.value, rule.err_message);
break;
case 'url':
validator = validator.url(rule.err_message);
break;
// Add more custom validators as needed
}
});
}
shape[field.name] = validator;
});
return Yup.object().shape(shape);
};
Step 5: Validate on Submit
const handleSubmit = async (e) => {
e.preventDefault();
const schema = buildValidationSchema();
try {
await schema.validate(formData, { abortEarly: false });
setErrors({});
console.log('Form submitted successfully', formData);
} catch (err) {
const formattedErrors = {};
err.inner.forEach(e => {
formattedErrors[e.path] = e.message;
});
setErrors(formattedErrors);
}
};
Common Mistakes (and How to Avoid Them)
❌ Hardcoding Fields
Never hardcode input elements if you plan to scale. Use config arrays or schema-based rendering.
❌ Not Handling Conditional Validations
Use requiredIf
logic for smart validations based on user selection.
❌ Validating on Every Key Press
Validate on form submit, or debounce validations if doing live field-level validation.
Add/Remove Fields Dynamically
Sometimes, users might want to add more items (e.g., multiple phone numbers). Here’s a quick idea:
const [phones, setPhones] = useState(['']);
const addPhone = () => setPhones([...phones, '']);
const removePhone = (index) => {
setPhones(phones.filter((_, i) => i !== index));
};
const handlePhoneChange = (value, index) => {
const updated = [...phones];
updated[index] = value;
setPhones(updated);
};
Render them like:
{phones.map((phone, idx) => (
<div key={idx}>
<input
value={phone}
onChange={e => handlePhoneChange(e.target.value, idx)}
/>
<button type="button" onClick={() => removePhone(idx)}>Remove</button>
</div>
))}
<button type="button" onClick={addPhone}>Add Phone</button>
FAQs (with FAQ Schema Markup)
What is a dynamic form in React?
A dynamic form is one that can render form fields based on external data or user interaction.
Can I build forms in React without a library?
Absolutely. You can manage state with useState
and validate using libraries like Yup.
What is the best way to handle dynamic validation?
Use a schema builder like Yup and conditionally apply required rules based on field values.
Conclusion
Dynamic forms are a vital part of any modern React app. They’re not as hard as they seem—you just need the right structure. From schema-driven rendering to real-time validation using Yup, this guide showed you how to build dynamic, scalable forms in React without relying on bulky libraries.
And honestly, once you’ve mastered this approach, you’ll wonder why you ever needed Formik.
👉 Got questions or want to share your implementation? Drop a comment below—let’s learn from each other.