Topic 05 / 14

Events & Forms

~10 min read  //  React Series  //  Coding India

Event Handlers

function Toolbar() {
  function handleSave() {
    console.log("saving…");
  }

  return (
    <>
      <button onClick={handleSave}>Save</button>
      <button onClick={() => console.log("inline")}>Log</button>
    </>
  );
}

Pass the function, don’t call it: onClick={handleSave} — with parentheses it would run on every render. To pass arguments, wrap in an arrow: onClick={() => remove(id)}.

Handlers receive the event object, same API as vanilla JS:

<input onKeyDown={(e) => e.key === "Enter" && submit()} />
<form onSubmit={(e) => { e.preventDefault(); save(); }}>

Controlled Inputs

In React, the input’s value lives in state — the input displays state, and typing updates state. One source of truth:

function SearchBox() {
  const [query, setQuery] = useState("");

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Search courses…"
    />
  );
}

Now the React state always knows what’s in the box — you can validate live, clear it programmatically (setQuery("")), or filter a list as the user types.

A Complete Form

function SignupForm({ onSubmit }) {
  const [form, setForm] = useState({ name: "", email: "", track: "django" });
  const [agreed, setAgreed] = useState(false);
  const [error, setError] = useState(null);

  function update(e) {
    const { name, value } = e.target;
    setForm({ ...form, [name]: value });      // one handler for all text fields
  }

  function handleSubmit(e) {
    e.preventDefault();
    if (!form.email.includes("@")) {
      setError("Enter a valid email");
      return;
    }
    setError(null);
    onSubmit(form);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={form.name} onChange={update} required />
      <input name="email" type="email" value={form.email} onChange={update} />

      <select name="track" value={form.track} onChange={update}>
        <option value="django">Django</option>
        <option value="react">React</option>
      </select>

      <label>
        <input
          type="checkbox"
          checked={agreed}
          onChange={(e) => setAgreed(e.target.checked)}
        />
        I agree to the terms
      </label>

      {error && <p className="error">{error}</p>}

      <button disabled={!agreed}>Sign up</button>
    </form>
  );
}

Patterns to note: the [name]: value computed key handles any number of text fields with one function; checkboxes use checked + e.target.checked; the button disables itself from state; errors are just state rendered conditionally.

Why Controlled Beats Uncontrolled

You can read inputs at submit time with refs/FormData (“uncontrolled”), and it’s fine for simple forms. Controlled inputs earn their keep the moment you need live validation, dependent fields, character counters, or instant filtering — which is most real forms. Libraries like react-hook-form optimise big forms, but they assume you understand this foundation.