Chapter 4 of 4

Submitting and Validating

Handling submit, showing validation messages, and a full working example.

Handle submission on the form's onSubmit, prevent the default reload, and read the values straight from state.

A complete controlled form with validation and a busy state.
import { useState } from "react";

function SignupForm({ onSignup }) {
  const [form, setForm] = useState({ email: "", password: "" });
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  function validate(values) {
    const found = {};
    if (!values.email.includes("@")) found.email = "Enter a valid email";
    if (values.password.length < 8) found.password = "Use at least 8 characters";
    return found;
  }

  async function handleSubmit(event) {
    event.preventDefault();
    const found = validate(form);
    setErrors(found);
    if (Object.keys(found).length > 0) return;

    setIsSubmitting(true);
    try {
      await onSignup(form);
    } finally {
      setIsSubmitting(false);
    }
  }

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

  return (
    <form onSubmit={handleSubmit} noValidate>
      <label htmlFor="email">Email</label>
      <input id="email" name="email" value={form.email} onChange={handleChange} />
      {errors.email && <p role="alert">{errors.email}</p>}

      <label htmlFor="password">Password</label>
      <input
        id="password"
        name="password"
        type="password"
        value={form.password}
        onChange={handleChange}
      />
      {errors.password && <p role="alert">{errors.password}</p>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Creating account..." : "Sign up"}
      </button>
    </form>
  );
}

Things worth copying from that example

  • htmlFor on every label, matching the input's id. This is what screen readers rely on.
  • role="alert" on error messages so they are announced when they appear.
  • disabled while submitting, to prevent double submissions.
  • A finally block, so a failed request does not leave the button stuck.

Write a validation function

Complete validateEmail so it returns an error message for invalid input and an empty string for valid input.