React Forms — Controlled Inputs and Validation (React 19+)

react forms tutorial — React Forms — Controlled Inputs and Validation (React 19+)

Written by

in

Updated July 4, 2026. Refreshed for React 19 and current SEO best practices.

React 19 Notes

Controlled inputs keep a single source of truth in React state — ideal for validation and reset. React 19 form actions (Server Actions) shine in Next.js; in Vite SPAs, useState remains the standard beginner pattern.

This lesson teaches React forms using controlled components — inputs whose value is driven by React state. You will handle submit events, validate fields, show error messages, and build a complete signup form.

Prerequisites: Lessons 1–5, especially useState. Estimated time: 45–55 minutes.

1. Controlled vs Uncontrolled Inputs

A controlled input stores its value in state and updates via onChange:

const [email, setEmail] = useState('');

<input
  type="email"
  value={email}
  onChange={(e) => setEmail(e.target.value)}
/>

An uncontrolled input uses a DOM ref — fine for simple cases, but controlled inputs are easier to validate and reset in React apps.

React controlled form with validation
Photo by Team Nocoloco on Unsplash

2. Form State as an Object

const [form, setForm] = useState({
  name: '',
  email: '',
  password: '',
});

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

Use the name attribute on each input to update the correct field without separate handlers.

User filling out a web form
Photo by Glenn Carstens-Peters on Unsplash

3. Handling Submit

function handleSubmit(e) {
  e.preventDefault();
  console.log('Submitting', form);
  // POST to API or update parent state
}

return (
  <form onSubmit={handleSubmit}>
    {/* inputs */}
    <button type="submit">Sign up</button>
  </form>
);

Always call e.preventDefault() unless you want a full page reload.

4. Simple Validation

const [errors, setErrors] = useState({});

function validate() {
  const next = {};
  if (!form.name.trim()) next.name = 'Name is required';
  if (!form.email.includes('@')) next.email = 'Valid email required';
  if (form.password.length < 8) next.password = 'Minimum 8 characters';
  setErrors(next);
  return Object.keys(next).length === 0;
}

function handleSubmit(e) {
  e.preventDefault();
  if (!validate()) return;
  // proceed with valid data
}

Show errors next to fields: {errors.email && <span>{errors.email}</span>}. For larger apps consider React Hook Form or Zod — out of scope for this beginner react forms tutorial.

5. Complete Example — SignupForm

import { useState } from 'react';

function SignupForm() {
  const [form, setForm] = useState({ name: '', email: '', password: '' });
  const [errors, setErrors] = useState({});
  const [submitted, setSubmitted] = useState(false);

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

  function validate() {
    const next = {};
    if (!form.name.trim()) next.name = 'Name is required';
    if (!form.email.includes('@')) next.email = 'Enter a valid email';
    if (form.password.length < 8) next.password = 'Use at least 8 characters';
    setErrors(next);
    return Object.keys(next).length === 0;
  }

  function handleSubmit(e) {
    e.preventDefault();
    if (!validate()) return;
    setSubmitted(true);
  }

  if (submitted) {
    return <p>Thanks, {form.name}! Check {form.email} to confirm.</p>;
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '360px' }}>
      <label>
        Name
        <input name="name" value={form.name} onChange={handleChange} />
        {errors.name && <small style={{ color: 'crimson' }}>{errors.name}</small>}
      </label>
      <label>
        Email
        <input name="email" type="email" value={form.email} onChange={handleChange} />
        {errors.email && <small style={{ color: 'crimson' }}>{errors.email}</small>}
      </label>
      <label>
        Password
        <input name="password" type="password" value={form.password} onChange={handleChange} />
        {errors.password && <small style={{ color: 'crimson' }}>{errors.password}</small>}
      </label>
      <button type="submit">Create account</button>
    </form>
  );
}

export default SignupForm;

Add basic CSS (stack labels with display: block and margin) and test invalid submissions to see validation messages.

6. Checkbox and Select

<input
  type="checkbox"
  checked={form.agree}
  onChange={(e) => setForm((prev) => ({ ...prev, agree: e.target.checked }))}
/>

<select
  name="plan"
  value={form.plan}
  onChange={handleChange}
>
  <option value="free">Free</option>
  <option value="pro">Pro</option>
</select>

7. Next Steps

Forms capture user input; routing sends users between pages. Next you will add React Router for multi-page navigation in a single-page app.

Series: Lesson 5 · Lesson 7 — React Router

Frequently Asked Questions

What is a controlled component in React?
A controlled component is an input whose value is stored in React state and updated through onChange, giving React full control over the field.

Should I use a form library?
For learning and small forms, plain useState is enough. Libraries like React Hook Form reduce re-renders and simplify validation in large apps.

How do I reset a form after submit?
Call setForm({ name: '', email: '', password: '' }) and setErrors({}) after a successful submit.


Want live React or frontend classes? Join Alkademy for instructor-led React and JavaScript courses with hands-on projects.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *