We use cookies to enhance your experience on the site
CodeWorlds

Forms in React - Controlled Components

On board the React spaceship, forms are like configuration panels - they serve to input navigation data, set mission parameters, and communicate with command headquarters. In this lesson, you will learn about controlled components - a pattern where React takes full control of form data, just like the onboard computer controls every ship system.

Controlled vs uncontrolled components

In React, we distinguish two approaches to form handling:

Controlled components - React manages the input value through state. Every change goes through

useState
and
onChange
:

1function ControlledInput() {
2  const [name, setName] = useState("");
3
4  return (
5    <input
6      type="text"
7      value={name}
8      onChange={(e) => setName(e.target.value)}
9    />
10  );
11}

Uncontrolled components - the browser manages the value, and we read it via

ref
:

1function UncontrolledInput() {
2  const inputRef = useRef(null);
3
4  const handleSubmit = () => {
5    console.log(inputRef.current.value);
6  };
7
8  return <input type="text" ref={inputRef} />;
9}

Why are controlled components better? Just as the ship's onboard computer must know the state of every system at all times, controlled components give React full visibility into the form data. This allows you to validate, format, and react to changes in real time.

Handling text fields with useState

Simple text input

1function PilotRegistration() {
2  const [pilotName, setPilotName] = useState("");
3  const [callSign, setCallSign] = useState("");
4
5  return (
6    <div className="registration-form">
7      <h2>Pilot Registration</h2>
8
9      <label>Pilot Name:</label>
10      <input
11        type="text"
12        value={pilotName}
13        onChange={(e) => setPilotName(e.target.value)}
14        placeholder="Enter name..."
15      />
16
17      <label>Call Sign:</label>
18      <input
19        type="text"
20        value={callSign}
21        onChange={(e) => setCallSign(e.target.value.toUpperCase())}
22        placeholder="E.g. ALPHA-7"
23      />
24
25      <p>Pilot: {pilotName} ({callSign})</p>
26    </div>
27  );
28}

Managing multiple fields with a single state

Instead of creating a separate

useState
for each field, you can use a single state object - like a central mission data registry:

1function MissionConfigForm() {
2  const [formData, setFormData] = useState({
3    missionName: "",
4    destination: "",
5    crewSize: "",
6    priority: "normal"
7  });
8
9  const handleChange = (event) => {
10    const { name, value } = event.target;
11    setFormData((prev) => ({
12      ...prev,
13      [name]: value
14    }));
15  };
16
17  return (
18    <div className="mission-config">
19      <h2>Mission Configuration</h2>
20
21      <label>Mission Name:</label>
22      <input
23        type="text"
24        name="missionName"
25        value={formData.missionName}
26        onChange={handleChange}
27      />
28
29      <label>Destination:</label>
30      <input
31        type="text"
32        name="destination"
33        value={formData.destination}
34        onChange={handleChange}
35      />
36
37      <label>Crew Size:</label>
38      <input
39        type="number"
40        name="crewSize"
41        value={formData.crewSize}
42        onChange={handleChange}
43      />
44
45      <label>Priority:</label>
46      <input
47        type="text"
48        name="priority"
49        value={formData.priority}
50        onChange={handleChange}
51      />
52
53      <pre>{JSON.stringify(formData, null, 2)}</pre>
54    </div>
55  );
56}

The key trick: the

name
attribute on inputs corresponds to keys in the state object, and
[name]: value
(computed property name) dynamically updates the corresponding field.

Checkboxes and select elements

Checkbox - system toggles

Checkboxes in React use

checked
instead of
value
:

1function ShipSystemsPanel() {
2  const [systems, setSystems] = useState({
3    engines: true,
4    shields: false,
5    weapons: false,
6    lifeSupport: true
7  });
8
9  const handleToggle = (event) => {
10    const { name, checked } = event.target;
11    setSystems((prev) => ({
12      ...prev,
13      [name]: checked
14    }));
15  };
16
17  return (
18    <div className="systems-panel">
19      <h2>Ship Systems</h2>
20
21      <label>
22        <input
23          type="checkbox"
24          name="engines"
25          checked={systems.engines}
26          onChange={handleToggle}
27        />
28        Engines
29      </label>
30
31      <label>
32        <input
33          type="checkbox"
34          name="shields"
35          checked={systems.shields}
36          onChange={handleToggle}
37        />
38        Shields
39      </label>
40
41      <label>
42        <input
43          type="checkbox"
44          name="weapons"
45          checked={systems.weapons}
46          onChange={handleToggle}
47        />
48        Weapon Systems
49      </label>
50
51      <label>
52        <input
53          type="checkbox"
54          name="lifeSupport"
55          checked={systems.lifeSupport}
56          onChange={handleToggle}
57        />
58        Life Support
59      </label>
60
61      <div className="status">
62        Active systems: {Object.entries(systems)
63          .filter(([, active]) => active)
64          .map(([name]) => name)
65          .join(", ")}
66      </div>
67    </div>
68  );
69}

Select - choosing from a list

The

<select>
element in React works like other controlled inputs - we control the value through
value
and
onChange
:

1function NavigationPanel() {
2  const [destination, setDestination] = useState("");
3  const [warpSpeed, setWarpSpeed] = useState("1");
4
5  const destinations = [
6    { value: "mars", label: "Mars" },
7    { value: "jupiter", label: "Jupiter" },
8    { value: "saturn", label: "Saturn" },
9    { value: "alpha-centauri", label: "Alpha Centauri" },
10    { value: "andromeda", label: "Andromeda Galaxy" }
11  ];
12
13  return (
14    <div className="nav-panel">
15      <h2>Navigation Panel</h2>
16
17      <label>Destination:</label>
18      <select
19        value={destination}
20        onChange={(e) => setDestination(e.target.value)}
21      >
22        <option value="">-- Select destination --</option>
23        {destinations.map((dest) => (
24          <option key={dest.value} value={dest.value}>
25            {dest.label}
26          </option>
27        ))}
28      </select>
29
30      <label>Warp Speed:</label>
31      <select
32        value={warpSpeed}
33        onChange={(e) => setWarpSpeed(e.target.value)}
34      >
35        <option value="1">Warp 1</option>
36        <option value="3">Warp 3</option>
37        <option value="5">Warp 5</option>
38        <option value="9">Warp 9 (maximum)</option>
39      </select>
40
41      {destination && (
42        <p>Course set: {destination} at Warp {warpSpeed}</p>
43      )}
44    </div>
45  );
46}

Handling form submission

Submitting a form is like issuing an order from command headquarters - it must be controlled and secure:

1function MissionReportForm() {
2  const [formData, setFormData] = useState({
3    missionName: "",
4    commander: "",
5    status: "in-progress",
6    description: "",
7    isClassified: false
8  });
9  const [submitted, setSubmitted] = useState(false);
10
11  const handleChange = (event) => {
12    const { name, value, type, checked } = event.target;
13    setFormData((prev) => ({
14      ...prev,
15      [name]: type === "checkbox" ? checked : value
16    }));
17  };
18
19  const handleSubmit = (event) => {
20    event.preventDefault();
21
22    // Validation
23    if (!formData.missionName.trim() || !formData.commander.trim()) {
24      alert("Please fill in all required fields!");
25      return;
26    }
27
28    console.log("Mission report:", formData);
29    setSubmitted(true);
30  };
31
32  const handleReset = () => {
33    setFormData({
34      missionName: "",
35      commander: "",
36      status: "in-progress",
37      description: "",
38      isClassified: false
39    });
40    setSubmitted(false);
41  };
42
43  if (submitted) {
44    return (
45      <div className="success">
46        <h2>Report Submitted!</h2>
47        <p>Mission: {formData.missionName}</p>
48        <button onClick={handleReset}>New Report</button>
49      </div>
50    );
51  }
52
53  return (
54    <form onSubmit={handleSubmit} className="mission-report">
55      <h2>Mission Report</h2>
56
57      <label>Mission Name *:</label>
58      <input
59        type="text"
60        name="missionName"
61        value={formData.missionName}
62        onChange={handleChange}
63        required
64      />
65
66      <label>Commander *:</label>
67      <input
68        type="text"
69        name="commander"
70        value={formData.commander}
71        onChange={handleChange}
72        required
73      />
74
75      <label>Status:</label>
76      <select name="status" value={formData.status} onChange={handleChange}>
77        <option value="in-progress">In Progress</option>
78        <option value="completed">Completed</option>
79        <option value="failed">Failed</option>
80      </select>
81
82      <label>Description:</label>
83      <textarea
84        name="description"
85        value={formData.description}
86        onChange={handleChange}
87        rows={4}
88        placeholder="Describe the mission progress..."
89      />
90
91      <label>
92        <input
93          type="checkbox"
94          name="isClassified"
95          checked={formData.isClassified}
96          onChange={handleChange}
97        />
98        Classified Report
99      </label>
100
101      <div className="form-actions">
102        <button type="submit">Submit Report</button>
103        <button type="button" onClick={handleReset}>Clear</button>
104      </div>
105    </form>
106  );
107}

Lifting state up

When multiple components need to share form data, we move the state to their common parent - like a command center coordinating data from multiple panels:

1function TemperatureInput({ label, value, onChange }) {
2  return (
3    <div>
4      <label>{label}:</label>
5      <input
6        type="number"
7        value={value}
8        onChange={(e) => onChange(e.target.value)}
9      />
10    </div>
11  );
12}
13
14function TemperatureConverter() {
15  const [celsius, setCelsius] = useState("");
16
17  const handleCelsiusChange = (value) => {
18    setCelsius(value);
19  };
20
21  const handleFahrenheitChange = (value) => {
22    const c = ((parseFloat(value) - 32) * 5) / 9;
23    setCelsius(isNaN(c) ? "" : c.toFixed(2));
24  };
25
26  const fahrenheit = celsius !== ""
27    ? ((parseFloat(celsius) * 9) / 5 + 32).toFixed(2)
28    : "";
29
30  return (
31    <div className="converter">
32      <h2>Hull Temperature Converter</h2>
33      <TemperatureInput
34        label="Celsius"
35        value={celsius}
36        onChange={handleCelsiusChange}
37      />
38      <TemperatureInput
39        label="Fahrenheit"
40        value={fahrenheit}
41        onChange={handleFahrenheitChange}
42      />
43    </div>
44  );
45}

In this pattern:

  • The state (
    celsius
    ) lives in the parent (
    TemperatureConverter
    )
  • The child components (
    TemperatureInput
    ) receive the value via props
  • Changes are passed upward through the
    onChange
    callback
  • The parent recalculates and synchronizes both values

Summary

Forms in React - key principles:

  1. Controlled components - React manages input values through
    value
    +
    onChange
  2. Single state for multiple fields - use a state object with dynamic
    [name]: value
  3. Checkboxes - use
    checked
    instead of
    value
    , read
    event.target.checked
  4. Select - control via
    value
    on
    <select>
    , not on
    <option>
  5. preventDefault() - always block the default form submission
  6. Validation - check data in the
    onSubmit
    handler before submitting
  7. Lifting state up - move state to a common parent when components need to share data

Remember: Controlled components are like the ship's control system - the onboard computer (React) knows about every change in real time and can react immediately!

Go to CodeWorlds