π What Is Form Handling in React?
Form handling refers to managing user input in a form (like text boxes, checkboxes, radio buttons, etc.) and responding to form submissions.
React handles forms using controlled components, where form inputs are tied to component state.
π§© Controlled vs Uncontrolled Components
Controlled Component:
The form input is connected to
useState.Example:
<input value={name} onChange={handleChange} />Uncontrolled Component:
React does not control the input; data is accessed via
refs. (More about this in coming chapters)
Beginners should start with controlled components for easier data tracking.
π§ Key Concepts in Form Handling
a. useState to store form values:
You store each inputβs value in a state variable.
const [name, setName] = useState('');b. onChange to handle user input:
Update state when user types or interacts.
<input value={name} onChange={(e) => setName(e.target.value)} />c. onSubmit to handle form submission:
Prevent default behavior and take action (like validation or sending data).
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form><div class="infographic-formbasics" id="fb">
<style>
.infographic-formbasics {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-deep: #1942a8;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--bad: #dc2626;
--bad-soft: #fde3e3;
--good: #16a34a;
--good-soft: #dcf5e3;
--radius: 8px;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 880px;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
color: var(--ink);
font-family: var(--body-font);
}
.infographic-formbasics[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #13171f;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-deep: #8fb4ff;
--accent-soft: #20293f;
--line: #2c313d;
--bad-soft: #3a1414;
--good-soft: #103a1e;
}
.infographic-formbasics * { box-sizing: border-box; min-width: 0; }
.fb-eyebrow {
font-family: var(--mono-font); font-size: 0.72rem; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--accent); margin: 0 0 0.45rem;
display: flex; align-items: center; justify-content: center; gap: 0.4rem;
}
.fb-eyebrow::before { content: ""; width: 14px; height: 2px; background: var(--accent); display: inline-block; }
.fb-title {
font-family: var(--display-font); font-size: clamp(1.4rem, 4vw, 2.15rem); text-align: center;
margin: 0 0 0.5rem; line-height: 1.18; letter-spacing: -0.01em;
}
.fb-sub { color: var(--ink-soft); font-size: 0.95rem; text-align: center; margin: 0 0 1.8rem; }
.fb-grid { display: flex; flex-direction: column; gap: 1.5rem; align-items: stretch; margin-bottom: 1.3rem; }
.fb-code-head { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.fb-code-head svg { width: 15px; height: 15px; flex-shrink: 0; }
.fb-code-name { font-size: 0.8rem; font-weight: 600; color: var(--ink); }
.fb-tag {
font-family: var(--mono-font); font-size: 0.65rem; font-weight: 700; border-radius: 100px;
padding: 0.15rem 0.55rem; background: var(--accent-soft); color: var(--accent-deep);
}
.fb-code-block pre {
margin: 0; background: #0d0d10; border: 1px solid #24252d; border-radius: var(--radius);
padding: 0.9rem; overflow: auto; max-height: 420px;
}
.fb-code-block code { font-family: var(--mono-font); font-size: 0.7rem; line-height: 1.7; color: #d4d4d8; white-space: pre; }
.tok-kw { color: #c678dd; }
.tok-tag { color: #e06c75; }
.tok-type { color: #61afef; }
.tok-attr { color: #d19a66; }
.tok-str { color: #98c379; }
.tok-expr { color: #56b6c2; }
.tok-punc { color: #6b7280; }
.tok-comment { color: #7f848e; font-style: italic; }
.fb-output-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; width: 100%; }
.fb-output-label {
font-family: var(--mono-font); font-size: 0.68rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-soft);
}
.fb-preview { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; width: 100%; box-shadow: 0 10px 24px -12px rgba(26,26,26,0.18); }
.fb-preview-bar { background: #e9e9e9; padding: 0.45rem 0.6rem; display: flex; gap: 0.3rem; }
.fb-preview-bar span { width: 8px; height: 8px; border-radius: 50%; display: block; }
.fb-preview-bar span:nth-child(1) { background: #ff5f56; }
.fb-preview-bar span:nth-child(2) { background: #ffbd2e; }
.fb-preview-bar span:nth-child(3) { background: #27c93f; }
.fb-preview-page { background: #fafaf8; padding: 1.6rem 1.6rem 1.8rem; }
/* --- the actual styled form --- */
.fb-form { display: flex; flex-direction: column; gap: 1.1rem; max-width: 420px; margin: 0 auto; }
.fb-field { display: flex; flex-direction: column; gap: 0.35rem; }
.fb-field label {
font-size: 0.78rem; font-weight: 600; color: var(--ink-soft); letter-spacing: 0.01em;
}
.fb-field input[type="text"], .fb-field textarea, .fb-field select {
font-family: var(--body-font); font-size: 0.9rem; color: var(--ink); background: #fff;
border: 1px solid var(--line); border-radius: 6px; padding: 0.55rem 0.7rem; width: 100%;
}
.fb-field input[type="text"]:focus, .fb-field textarea:focus, .fb-field select:focus {
outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);
}
.fb-field textarea { resize: vertical; min-height: 70px; }
.fb-checkbox-row {
display: flex; align-items: center; gap: 0.55rem; font-size: 0.88rem; color: var(--ink);
}
.fb-checkbox-row input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
.fb-fieldset { border: 1px solid var(--line); border-radius: 6px; padding: 0.7rem 0.9rem; margin: 0; }
.fb-fieldset legend {
font-size: 0.78rem; font-weight: 600; color: var(--ink-soft); padding: 0 0.35rem;
}
.fb-radio-row { display: flex; align-items: center; gap: 0.5rem; font-size: 0.88rem; color: var(--ink); padding: 0.2rem 0; }
.fb-radio-row input { width: 15px; height: 15px; accent-color: var(--accent); cursor: pointer; }
.fb-submit-btn {
font: inherit; font-weight: 600; font-size: 0.9rem; color: #fff; background: var(--accent);
border: none; border-radius: 100px; padding: 0.65rem 1.4rem; cursor: pointer; align-self: flex-start;
box-shadow: 0 6px 16px -4px rgba(37,99,235,0.45); transition: transform 0.15s, background 0.15s;
}
.fb-submit-btn:hover { transform: translateY(-1px); }
.fb-submit-btn:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 2px; }
.fb-state-panel {
margin-top: 0.4rem; border-top: 1px dashed var(--line); padding-top: 0.9rem;
}
.fb-state-label {
font-family: var(--mono-font); font-size: 0.65rem; letter-spacing: 0.08em; text-transform: uppercase;
color: var(--ink-soft); margin: 0 0 0.4rem; display: flex; align-items: center; gap: 0.4rem;
}
.fb-live-dot {
width: 6px; height: 6px; border-radius: 50%; background: var(--good); display: inline-block;
animation: fb-pulse 1.6s ease-in-out infinite;
}
@keyframes fb-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.fb-state-pre {
font-family: var(--mono-font); font-size: 0.72rem; line-height: 1.6; color: var(--ink);
background: var(--paper-alt); border-radius: 6px; padding: 0.7rem 0.85rem; margin: 0; overflow: auto;
}
.fb-caption {
text-align: center; font-size: 0.9rem; color: var(--ink); min-height: 2.2em; display: flex;
align-items: center; justify-content: center; max-width: 560px; margin: 0.9rem auto 1.6rem; line-height: 1.45;
}
.fb-takeaway {
padding: 0.95rem 1.2rem; background: var(--accent-soft); border-left: 3px solid var(--accent);
border-radius: 4px; font-size: 0.9rem; color: var(--ink); line-height: 1.5; max-width: 640px; margin: 0 auto;
}
.fb-takeaway b { color: var(--accent-deep); }
.fb-takeaway code { font-family: var(--mono-font); }
@media (prefers-reduced-motion: reduce) {
.infographic-formbasics *, .infographic-formbasics *::before, .infographic-formbasics *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
.fb-live-dot { animation: none; }
}
</style>
<div class="fb-eyebrow">Controlled Components</div>
<h1 class="fb-title">Basics of Form Handling in React</h1>
<p class="fb-sub">Every field below is wired to state. Fill it out and watch the state update live.</p>
<div class="fb-grid">
<div class="fb-code-block">
<div class="fb-code-head">
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6 3 L2 8 L6 13" fill="none" stroke="var(--accent)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 3 L14 8 L10 13" fill="none" stroke="var(--accent)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span class="fb-code-name">FormBasics.jsx</span>
<span class="fb-tag">controlled</span>
</div>
<pre><code id="fbCode"></code></pre>
</div>
<div class="fb-output-wrap">
<div class="fb-output-label">Output</div>
<div class="fb-preview">
<div class="fb-preview-bar"><span></span><span></span><span></span></div>
<div class="fb-preview-page">
<form class="fb-form" id="fbForm">
<div class="fb-field">
<label for="fbName">Name</label>
<input type="text" id="fbName" name="name" placeholder="Your name" />
</div>
<div class="fb-field">
<label for="fbBio">Bio</label>
<textarea id="fbBio" name="bio" placeholder="A little about you"></textarea>
</div>
<div class="fb-field">
<label for="fbCountry">Country</label>
<select id="fbCountry" name="country">
<option value="india">India</option>
<option value="usa">USA</option>
<option value="uk">UK</option>
</select>
</div>
<label class="fb-checkbox-row" for="fbSubscribe">
<input type="checkbox" id="fbSubscribe" name="subscribe" />
Subscribe to newsletter
</label>
<fieldset class="fb-fieldset">
<legend>Gender</legend>
<label class="fb-radio-row"><input type="radio" name="gender" value="female" /> Female</label>
<label class="fb-radio-row"><input type="radio" name="gender" value="male" /> Male</label>
<label class="fb-radio-row"><input type="radio" name="gender" value="other" /> Other</label>
</fieldset>
<button class="fb-submit-btn" type="button" id="fbSubmitBtn">Submit</button>
<div class="fb-state-panel">
<p class="fb-state-label">formData (on submit)</p>
<pre class="fb-state-pre" id="fbStatePre">// submit the form to see it here</pre>
</div>
</form>
</div>
</div>
</div>
</div>
<p class="fb-caption" id="fbCaption">Fill in the fields, then hit Submit to see the collected formData.</p>
<div class="fb-takeaway"><b>One pattern, five elements.</b> Text and textarea read <code>value</code>/<code>onChange</code>; select does the same on the <code><select></code> itself, not the options; checkboxes swap in <code>checked</code>/<code>e.target.checked</code>; radios share one <code>name</code> and compare <code>value</code> against state. Submit just reads whatever state already holds.</div>
</div>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@500;600&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
(function () {
var root = document.getElementById('fb');
if (!root) return;
/* --- dark mode detection --- */
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) root.setAttribute('data-theme', 'dark');
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'darkMode') {
if (e.data.value) root.setAttribute('data-theme', 'dark');
else root.removeAttribute('data-theme');
}
});
var code =
'<span class="tok-kw">import</span> <span class="tok-type">React</span><span class="tok-punc">,</span> <span class="tok-punc">{</span> useState <span class="tok-punc">}</span> <span class="tok-kw">from</span> <span class="tok-str">"react"</span><span class="tok-punc">;</span>\n\n' +
'<span class="tok-kw">function</span> <span class="tok-type">FormBasics</span><span class="tok-punc">() {</span>\n' +
' <span class="tok-kw">const</span> <span class="tok-punc">[</span>formData<span class="tok-punc">,</span> setFormData<span class="tok-punc">]</span> <span class="tok-punc">=</span> useState<span class="tok-punc">({</span>\n' +
' <span class="tok-attr">name</span><span class="tok-punc">:</span> <span class="tok-str">""</span><span class="tok-punc">,</span> <span class="tok-comment">// text input</span>\n' +
' <span class="tok-attr">bio</span><span class="tok-punc">:</span> <span class="tok-str">""</span><span class="tok-punc">,</span> <span class="tok-comment">// textarea</span>\n' +
' <span class="tok-attr">country</span><span class="tok-punc">:</span> <span class="tok-str">"india"</span><span class="tok-punc">,</span> <span class="tok-comment">// select</span>\n' +
' <span class="tok-attr">subscribe</span><span class="tok-punc">:</span> <span class="tok-kw">false</span><span class="tok-punc">,</span> <span class="tok-comment">// checkbox</span>\n' +
' <span class="tok-attr">gender</span><span class="tok-punc">:</span> <span class="tok-str">""</span><span class="tok-punc">,</span> <span class="tok-comment">// radio group</span>\n' +
' <span class="tok-punc">});</span>\n\n' +
' <span class="tok-comment">// text, textarea, select, radio share this handler</span>\n' +
' <span class="tok-kw">function</span> handleChange<span class="tok-punc">(</span>e<span class="tok-punc">) {</span>\n' +
' <span class="tok-kw">const</span> <span class="tok-punc">{</span> name<span class="tok-punc">,</span> value <span class="tok-punc">}</span> <span class="tok-punc">=</span> e.target<span class="tok-punc">;</span>\n' +
' setFormData<span class="tok-punc">((</span>prev<span class="tok-punc">) => ({</span> ...prev<span class="tok-punc">,</span> <span class="tok-punc">[</span>name<span class="tok-punc">]:</span> value <span class="tok-punc">}));</span>\n' +
' <span class="tok-punc">}</span>\n\n' +
' <span class="tok-comment">// checkboxes use `checked`, not `value`</span>\n' +
' <span class="tok-kw">function</span> handleCheckboxChange<span class="tok-punc">(</span>e<span class="tok-punc">) {</span>\n' +
' <span class="tok-kw">const</span> <span class="tok-punc">{</span> name<span class="tok-punc">,</span> checked <span class="tok-punc">}</span> <span class="tok-punc">=</span> e.target<span class="tok-punc">;</span>\n' +
' setFormData<span class="tok-punc">((</span>prev<span class="tok-punc">) => ({</span> ...prev<span class="tok-punc">,</span> <span class="tok-punc">[</span>name<span class="tok-punc">]:</span> checked <span class="tok-punc">}));</span>\n' +
' <span class="tok-punc">}</span>\n\n' +
' <span class="tok-kw">function</span> handleSubmit<span class="tok-punc">(</span>e<span class="tok-punc">) {</span>\n' +
' e.preventDefault<span class="tok-punc">();</span>\n' +
' <span class="tok-comment">// formData already holds everything the form needs</span>\n' +
' submitToServer<span class="tok-punc">(</span>formData<span class="tok-punc">);</span>\n' +
' <span class="tok-punc">}</span>\n\n' +
' <span class="tok-kw">return</span> <span class="tok-punc">(</span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">form</span> <span class="tok-attr">onSubmit</span><span class="tok-punc">={</span>handleSubmit<span class="tok-punc">}></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">input</span> <span class="tok-attr">name</span><span class="tok-punc">=</span><span class="tok-str">"name"</span> <span class="tok-attr">value</span><span class="tok-punc">={</span>formData.name<span class="tok-punc">}</span> <span class="tok-attr">onChange</span><span class="tok-punc">={</span>handleChange<span class="tok-punc">}</span> <span class="tok-punc">/></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">textarea</span> <span class="tok-attr">name</span><span class="tok-punc">=</span><span class="tok-str">"bio"</span> <span class="tok-attr">value</span><span class="tok-punc">={</span>formData.bio<span class="tok-punc">}</span> <span class="tok-attr">onChange</span><span class="tok-punc">={</span>handleChange<span class="tok-punc">}</span> <span class="tok-punc">/></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">select</span> <span class="tok-attr">name</span><span class="tok-punc">=</span><span class="tok-str">"country"</span> <span class="tok-attr">value</span><span class="tok-punc">={</span>formData.country<span class="tok-punc">}</span> <span class="tok-attr">onChange</span><span class="tok-punc">={</span>handleChange<span class="tok-punc">}</span><span class="tok-punc">></span> ... <span class="tok-punc"></</span><span class="tok-tag">select</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">input</span> <span class="tok-attr">type</span><span class="tok-punc">=</span><span class="tok-str">"checkbox"</span> <span class="tok-attr">name</span><span class="tok-punc">=</span><span class="tok-str">"subscribe"</span> <span class="tok-attr">checked</span><span class="tok-punc">={</span>formData.subscribe<span class="tok-punc">}</span> <span class="tok-attr">onChange</span><span class="tok-punc">={</span>handleCheckboxChange<span class="tok-punc">}</span> <span class="tok-punc">/></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">input</span> <span class="tok-attr">type</span><span class="tok-punc">=</span><span class="tok-str">"radio"</span> <span class="tok-attr">name</span><span class="tok-punc">=</span><span class="tok-str">"gender"</span> <span class="tok-attr">value</span><span class="tok-punc">=</span><span class="tok-str">"female"</span> <span class="tok-attr">checked</span><span class="tok-punc">={</span>formData.gender <span class="tok-punc">===</span> <span class="tok-str">"female"</span><span class="tok-punc">}</span> <span class="tok-attr">onChange</span><span class="tok-punc">={</span>handleChange<span class="tok-punc">}</span> <span class="tok-punc">/></span>\n' +
' <span class="tok-punc"><</span><span class="tok-tag">button</span> <span class="tok-attr">type</span><span class="tok-punc">=</span><span class="tok-str">"submit"</span><span class="tok-punc">></span>Submit<span class="tok-punc"></</span><span class="tok-tag">button</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc"></</span><span class="tok-tag">form</span><span class="tok-punc">></span>\n' +
' <span class="tok-punc">);</span>\n' +
'<span class="tok-punc">}</span>\n\n' +
'<span class="tok-kw">export</span> <span class="tok-kw">default</span> <span class="tok-type">FormBasics</span><span class="tok-punc">;</span>';
root.querySelector('#fbCode').innerHTML = code;
var form = root.querySelector('#fbForm');
var statePre = root.querySelector('#fbStatePre');
var caption = root.querySelector('#fbCaption');
var formData = { name: '', bio: '', country: 'india', subscribe: false, gender: '' };
// formData is still updated on every change (this is what "controlled
// components" means in React) β we just don't render it until submit.
form.addEventListener('input', function (e) {
var el = e.target;
if (!el.name) return;
if (el.type === 'checkbox') formData[el.name] = el.checked;
else formData[el.name] = el.value;
});
var submitBtn = root.querySelector('#fbSubmitBtn');
submitBtn.addEventListener('click', function () {
statePre.textContent = JSON.stringify(formData, null, 2);
caption.textContent = 'β
Submitted β handleSubmit just read whatever formData already held.';
caption.style.color = 'var(--good)';
setTimeout(function () {
caption.style.color = '';
caption.textContent = 'Fill in the fields, then hit Submit to see the collected formData.';
}, 2400);
});
})();
</script>β Why Use Controlled Components?
React is fully in control of the form state.
Easier to implement validation, conditional UI, and form resets.
Enables real-time input processing (e.g., live search, character limits).
π Common Form Elements Handled in React
<input type="text">β for text values<textarea>β for multiline text<select>and<option>β for dropdowns<input type="checkbox">β usecheckedinstead ofvalue<input type="radio">β usually paired with anameattribute