Skip to main content
Back to module
React Fundamentals · State and Events

Events and forms

Reading42mLesson 2 of 3 in this module

Handle clicks and typing, and build a controlled form whose state drives validation and submission.

Why it matters

Sign-up pages, search boxes, filters, checkout steps: most of the interaction in an app is people clicking and typing. React handles events with props such as onClick and onChange, and handles form fields with controlled components, where React state is the single source of truth for what is in each input.

Handling events

Pass a function to an event prop. React calls it with an event object:

function SaveButton() {
  function handleClick(event) {
    console.log("Saved!", event.type); // "click"
  }
  return <button onClick={handleClick}>Save</button>;
}
  • Event props are camelCase: onClick, onChange, onSubmit, onKeyDown, onFocus.
  • Pass the function, do not call it: onClick={handleClick}, not onClick={handleClick()}.
  • To pass arguments, wrap it: onClick={() => removeItem(item.id)}.
  • event.preventDefault() stops the browser's default action (following a link, submitting a form). event.stopPropagation() stops the event bubbling to parent elements.

React's event object wraps the browser's native event and works the same across browsers.

Controlled inputs

In plain HTML an input keeps its own value. In React you usually keep the value in state and feed it back to the input:

function NameField() {
  const [name, setName] = React.useState("");
  return (
    <label>
      Name
      <input value={name} onChange={(e) => setName(e.target.value)} />
    </label>
  );
}

This loop is the core of controlled components:

  1. The input shows value={name}.
  2. The user types; React fires onChange with the new text in e.target.value.
  3. You call setName, React re-renders, and the input shows the new value.

If you set value without onChange, the input becomes read-only, and React warns you. Because the value lives in state, you can validate, transform (for example e.target.value.toUpperCase()), reset or pre-fill it easily.

In React, onChange fires on every keystroke, like the DOM input event, not only when the field loses focus.

Other field types

<textarea value={bio} onChange={(e) => setBio(e.target.value)} />

<select value={plan} onChange={(e) => setPlan(e.target.value)}>
  <option value="free">Free</option>
  <option value="pro">Pro</option>
</select>

<input
  type="checkbox"
  checked={agree}
  onChange={(e) => setAgree(e.target.checked)}
/>

Checkboxes use checked and e.target.checked instead of value.

One object for many fields

For larger forms, keep the fields in one object and use the input's name to update the right key:

const [form, setForm] = React.useState({ name: "", email: "" });

function handleChange(e) {
  const { name, value } = e.target;
  setForm((f) => ({ ...f, [name]: value }));
}

<input name="name" value={form.name} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />

Validation and submitting

Validation rules are derived from state, so compute them during render:

const valid = form.name.trim() !== "" && form.email.includes("@");

function handleSubmit(e) {
  e.preventDefault(); // stay on the page
  if (!valid) return;
  onSignup({ name: form.name.trim(), email: form.email.trim() });
  setForm({ name: "", email: "" }); // clear the fields
}

return (
  <form onSubmit={handleSubmit}>
    ...
    <button type="submit" disabled={!valid}>Sign up</button>
  </form>
);

Using a real <form> with onSubmit is best in apps: pressing Enter submits and screen readers understand the structure. (The exercise preview on this site blocks form submission, so the Do tab uses a type="button" with onClick instead.)

Client-side checks are for a pleasant experience only. The server must validate again, because anyone can send requests without your form.

Accessible forms

  • Every input needs a label. Wrap the input in <label> or connect them with htmlFor and id.
  • Show errors as text near the field, not just a red border, and use role="alert" or role="status" for messages that appear after an action so screen readers announce them.
  • A disabled button gives no hint why. For important forms, also show what is missing.

Uncontrolled inputs

Sometimes you only need the value at submit time. You can let the DOM keep it and read it with a ref (React.useRef) or new FormData(e.target). That is an uncontrolled input. Controlled inputs are the default choice when the UI reacts to what the user types: live validation, disabling buttons, filtering a list.

Common mistakes

  • value without onChange (read-only field) or switching an input between value={undefined} and a string (React warns about changing from uncontrolled to controlled). Start with "".
  • Forgetting e.preventDefault() in onSubmit, so the page reloads.
  • Using value on a checkbox instead of checked.
  • Trusting client-side validation as security.

Recap

  • Event props take a function; use arrow functions to pass arguments.
  • Controlled inputs: value from state plus onChange that sets state.
  • Derive validity from state; clear or reset by setting state.
  • Label every field and announce results; validate again on the server.

Try it

Open the Do tab to build a controlled sign-up form whose button enables only when the fields are valid, and that clears itself after a successful sign-up.

Also in this lesson: