🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
REFERENCEreact

react Documentation

LOADING ENGINE...

Form Handling

AI & DATA SCIENCE // form-handling

Form handling in React means managing an input's value with state and processing the submitted data through JavaScript, rather than relying on the browser's default HTML form submission.

Syntax

<form onSubmit={handleSubmit}>
  <input value={value} onChange={handleChange} />
</form>

Deep Dive Course

A typical React form pairs controlled inputs, whose value comes from state and whose onChange handler updates that state, with an onSubmit handler on the form itself that calls event.preventDefault() to stop the browser's default full-page submission, then processes the current state values however the application needs, like sending them to an API. For a form with several fields, it's common to store all the field values together in a single state object, updating just the relevant key on each field's change, rather than one separate useState call per field.

1Understanding Form Handling

A typical React form pairs controlled inputs, whose value comes from state and whose onChange handler updates that state, with an onSubmit handler on the form itself that calls event.preventDefault() to stop the browser's default full-page submission, then processes the current state values however the application needs, like sending them to an API. For a form with several fields, it's common to store all the field values together in a single state object, updating just the relevant key on each field's change, rather than one separate useState call per field.

💡

For a form with many fields, store them together as one state object and update it with the spread operator, setForm({ ...form, [name]: value }), keyed by each input's name attribute — this scales far better than one separate useState call per field as a form grows.

editor.html
import { useState } from 'react';

function ContactForm() {
  const [form, setForm] = useState({ name: '', email: '' });
  const handleChange = (e) => setForm({ ...form, [e.target.name]: e.target.value });
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Submitting:', form);
  };
  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={form.name} onChange={handleChange} />
      <input name="email" value={form.email} onChange={handleChange} />
      <button type="submit">Send</button>
    </form>
  );
}
localhost:3000

2Practical Example

Here is a real-world application of Form Handling showing how it is used in production React code.

editor.html
// Typing 'A' into the name field triggers handleChange with e.target.name === 'name'
// and updates only that key, leaving email untouched
console.log({ ...{ name: '', email: '' }, name: 'A' });
localhost:3000

3Best Practices

Follow these guidelines when working with Form Handling:

1. Store multiple related form fields together in one state object, updating the changed field via its name attribute and the spread operator, rather than one useState call per field

2. Call event.preventDefault() at the start of the onSubmit handler to prevent the browser's default full-page form submission

3. Validate and process form data only after preventDefault() has stopped the default submission, keeping all the actual submission logic in JavaScript

⚠️

Tip: For a form with many fields, store them together as one state object and update it with the spread operator, setForm({ ...form, [name]: value }), keyed by each input's name attribute — this scales far better than one separate useState call per field as a form grows.

editor.html
import { useState } from 'react';

function ContactForm() {
  const [form, setForm] = useState({ name: '', email: '' });
  const handleChange = (e) => setForm({ ...form, [e.target.name]: e.target.value });
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Submitting:', form);
  };
  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={form.name} onChange={handleChange} />
      <input name="email" value={form.email} onChange={handleChange} />
      <button type="submit">Send</button>
    </form>
  );
}
localhost:3000

Examples

Example 01Basic Usage
import { useState } from 'react';

function ContactForm() {
  const [form, setForm] = useState({ name: '', email: '' });
  const handleChange = (e) => setForm({ ...form, [e.target.name]: e.target.value });
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Submitting:', form);
  };
  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={form.name} onChange={handleChange} />
      <input name="email" value={form.email} onChange={handleChange} />
      <button type="submit">Send</button>
    </form>
  );
}
Example 02Advanced Example
// Typing 'A' into the name field triggers handleChange with e.target.name === 'name'
// and updates only that key, leaving email untouched
console.log({ ...{ name: '', email: '' }, name: 'A' });

Best Practices

  • Store multiple related form fields together in one state object, updating the changed field via its name attribute and the spread operator, rather than one useState call per field
  • Call event.preventDefault() at the start of the onSubmit handler to prevent the browser's default full-page form submission
  • Validate and process form data only after preventDefault() has stopped the default submission, keeping all the actual submission logic in JavaScript

Interview Question

Why does using a single state object with a computed key, setForm({ ...form, [e.target.name]: e.target.value }), scale better than a separate useState call for each individual form field?

Hint: Think about how many onChange handlers you'd need to write, and how that number grows, under each approach as a form gains more fields.

With one useState call per field, every single input needs its own dedicated state variable and typically its own dedicated onChange handler referencing that specific variable and its setter, meaning adding a new field to the form requires writing an entirely new state declaration and handler each time, and the amount of repetitive boilerplate grows linearly with the number of fields. Using one shared state object with a single onChange handler that reads the changed input's name attribute and uses it as a computed object key, [e.target.name]: e.target.value, lets exactly one handler function correctly update whichever specific field actually changed, regardless of how many total fields the form has, since the input's own name attribute tells the handler which key to update. This means adding another field to the form only requires adding another input element using the same shared handler, without writing any new state or handler logic at all, which is exactly why this pattern scales more gracefully as a form grows to have many fields.

Exercises

MediumPractice using Form Handling in a real scenario.
View Solution
import { useState } from 'react';

function ContactForm() {
  const [form, setForm] = useState({ name: '', email: '' });
  const handleChange = (e) => setForm({ ...form, [e.target.name]: e.target.value });
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Submitting:', form);
  };
  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={form.name} onChange={handleChange} />
      <input name="email" value={form.email} onChange={handleChange} />
      <button type="submit">Send</button>
    </form>
  );
}

Frequently Asked Questions

Why does using a single state object with a computed key, setForm({ ...form, [e.target.name]: e.target.value }), scale better than a separate useState call for each individual form field?

With one useState call per field, every single input needs its own dedicated state variable and typically its own dedicated onChange handler referencing that specific variable and its setter, meaning adding a new field to the form requires writing an entirely new state declaration and handler each time, and the amount of repetitive boilerplate grows linearly with the number of fields. Using one shared state object with a single onChange handler that reads the changed input's name attribute and uses it as a computed object key, [e.target.name]: e.target.value, lets exactly one handler function correctly update whichever specific field actually changed, regardless of how many total fields the form has, since the input's own name attribute tells the handler which key to update. This means adding another field to the form only requires adding another input element using the same shared handler, without writing any new state or handler logic at all, which is exactly why this pattern scales more gracefully as a form grows to have many fields.

Related Functions

usestate-in-formscontrolled-vs-uncontrolledform-validation