A compact, copy-paste-ready dev.to post that shows how to build adaptive forms in React using controlled components and conditional rendering. Drop the code into a create-react-app project's src/App.jsx (or paste directly into CodeSandbox) and you're ready to go.
Why adaptive forms?
Adaptive forms show or hide fields based on user choices. They're great for reducing friction (only ask what you need) and improving accessibility when implemented correctly.
This example covers:
- Controlled inputs with
useState - Conditional rendering (show/hide sections)
- Simple validation on submit
- Dynamic repeating fields (hobbies)
- Accessible labels and brief UX polish
One-file copy-paste React code
Paste the entire block below into
src/App.jsxof a Create React App, or into CodeSandbox. It uses only React (no external libs).
import React, { useState } from 'react';
export default function AdaptiveForm() {
const [form, setForm] = useState({
name: '',
age: '',
contactMethod: 'email', // 'email' | 'phone'
email: '',
phone: '',
employed: 'no', // 'yes' | 'no'
company: '',
title: '',
hobbies: [''],
});
const [errors, setErrors] = useState({});
const [submittedData, setSubmittedData] = useState(null);
function handleChange(e) {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
}
function handleHobbyChange(index, value) {
setForm(prev => {
const hobbies = [...prev.hobbies];
hobbies[index] = value;
return { ...prev, hobbies };
});
}
function addHobby() {
setForm(prev => ({ ...prev, hobbies: [...prev.hobbies, ''] }));
}
function removeHobby(i) {
setForm(prev => ({ ...prev, hobbies: prev.hobbies.filter((_, idx) => idx !== i) }));
}
function validate() {
const err = {};
if (!form.name.trim()) err.name = 'Name is required.';
if (!form.age || Number(form.age) <= 0) err.age = 'Enter a valid age.';
if (form.contactMethod === 'email') {
if (!form.email.trim()) err.email = 'Email is required.';
else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email)) err.email = 'Invalid email.';
} else {
if (!form.phone.trim()) err.phone = 'Phone is required.';
else if (!/^\+?\d{7,15}$/.test(form.phone)) err.phone = 'Enter phone with country code (digits only).';
}
if (form.employed === 'yes') {
if (!form.company.trim()) err.company = 'Company name required.';
if (!form.title.trim()) err.title = 'Job title required.';
}
return err;
}
function handleSubmit(e) {
e.preventDefault();
const v = validate();
setErrors(v);
if (Object.keys(v).length === 0) {
// Clean empty hobby entries
const cleaned = { ...form, hobbies: form.hobbies.filter(h => h && h.trim().length > 0) };
setSubmittedData(cleaned);
} else {
setSubmittedData(null);
}
}
return (
<div style={{ maxWidth: 780, margin: '24px auto', fontFamily: 'system-ui, Arial', padding: 18 }}>
<h1 style={{ fontSize: 26 }}>Adaptive form demo</h1>
<p style={{ color: '#444' }}>This form adapts to user choices (contact method, employment) and supports dynamic fields.</p>
<form onSubmit={handleSubmit} noValidate>
<label style={{ display: 'block', marginTop: 12 }}>
Name
<input
name="name"
value={form.name}
onChange={handleChange}
style={{ display: 'block', width: '100%', padding: 8, marginTop: 6 }}
aria-invalid={!!errors.name}
aria-describedby={errors.name ? 'err-name' : undefined}
/>
</label>
{errors.name && <div id="err-name" style={{ color: 'crimson', fontSize: 13 }}>{errors.name}</div>}
<label style={{ display: 'block', marginTop: 12 }}>
Age
<input
name="age"
type="number"
min="0"
value={form.age}
onChange={handleChange}
style={{ display: 'block', width: 120, padding: 8, marginTop: 6 }}
aria-invalid={!!errors.age}
aria-describedby={errors.age ? 'err-age' : undefined}
/>
</label>
{errors.age && <div id="err-age" style={{ color: 'crimson', fontSize: 13 }}>{errors.age}</div>}
<fieldset style={{ border: 'none', padding: 0, marginTop: 12 }}>
<legend style={{ fontWeight: 600 }}>Preferred contact method</legend>
<label style={{ marginRight: 12 }}>
<input
type="radio"
name="contactMethod"
value="email"
checked={form.contactMethod === 'email'}
onChange={handleChange}
/>{' '}
Email
</label>
<label>
<input
type="radio"
name="contactMethod"
value="phone"
checked={form.contactMethod === 'phone'}
onChange={handleChange}
/>{' '}
Phone
<
Top comments (0)