PBPrep Bookfull-stack interview

Chapter 16 — Controlled vs Uncontrolled Components

📖 Definition

  • Controlled component — form value is driven by React state. Single source of truth.
  • Uncontrolled component — form value is managed by the DOM; React reads it via ref.

🔍 Explanation

Controlled Uncontrolled
Value source React state DOM
Read value From state ref.current.value
Validation Easy (on every change) On submit / blur
Best for Production forms Simple/legacy inputs, file uploads
Re-renders One per keystroke Zero

💻 Code Example — Controlled Input

function Login() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const onSubmit = (e) => {
    e.preventDefault();
    console.log({ email, password });
  };

  return (
    <form onSubmit={onSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Login</button>
    </form>
  );
}

💻 Code Example — Uncontrolled Input

function Login() {
  const emailRef = useRef();
  const passwordRef = useRef();

  const onSubmit = (e) => {
    e.preventDefault();
    console.log({
      email: emailRef.current.value,
      password: passwordRef.current.value,
    });
  };

  return (
    <form onSubmit={onSubmit}>
      <input type="email"    ref={emailRef}    defaultValue="" />
      <input type="password" ref={passwordRef} defaultValue="" />
      <button type="submit">Login</button>
    </form>
  );
}

🔑 Use defaultValue (not value) for uncontrolled inputs.

💻 Code Example — Validation in a Controlled Form

function Signup() {
  const [form, setForm] = useState({ email: "", password: "" });
  const [errors, setErrors] = useState({});

  const update = (field) => (e) => {
    const value = e.target.value;
    setForm(prev => ({ ...prev, [field]: value }));
    setErrors(prev => ({ ...prev, [field]: validate(field, value) }));
  };

  const validate = (field, value) => {
    if (field === "email" && !/^\S+@\S+$/.test(value)) return "Invalid email";
    if (field === "password" && value.length < 8) return "Min 8 characters";
    return undefined;
  };

  const isValid = Object.values(errors).every(e => !e) && form.email && form.password;

  return (
    <form>
      <input value={form.email} onChange={update("email")} />
      {errors.email && <span className="err">{errors.email}</span>}

      <input value={form.password} onChange={update("password")} type="password" />
      {errors.password && <span className="err">{errors.password}</span>}

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

💻 Code Example — File Input (Must Be Uncontrolled)

function UploadForm() {
  const fileRef = useRef();

  const onSubmit = (e) => {
    e.preventDefault();
    const file = fileRef.current.files[0];
    const formData = new FormData();
    formData.append("file", file);
    fetch("/upload", { method: "POST", body: formData });
  };

  return (
    <form onSubmit={onSubmit}>
      <input type="file" ref={fileRef} accept="image/*" />
      <button type="submit">Upload</button>
    </form>
  );
}

File inputs cannot be controlled — the browser owns the value for security reasons.

💻 Code Example — Checkbox & Select (Controlled)

function Preferences() {
  const [newsletter, setNewsletter] = useState(true);
  const [country, setCountry] = useState("IN");

  return (
    <>
      <label>
        <input
          type="checkbox"
          checked={newsletter}
          onChange={(e) => setNewsletter(e.target.checked)}
        />
        Subscribe to newsletter
      </label>

      <select value={country} onChange={(e) => setCountry(e.target.value)}>
        <option value="IN">India</option>
        <option value="US">USA</option>
        <option value="UK">UK</option>
      </select>
    </>
  );
}

💻 Code Example — Form Libraries (React Hook Form)

For larger forms, libraries reduce boilerplate:

import { useForm } from "react-hook-form";

function Signup() {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email", { required: true, pattern: /\S+@\S+/ })} />
      {errors.email && <span>Email invalid</span>}

      <input type="password" {...register("password", { minLength: 8 })} />
      {errors.password && <span>Min 8 chars</span>}

      <button type="submit">Sign up</button>
    </form>
  );
}

react-hook-form uses uncontrolled inputs under the hood for performance, but exposes a controlled-like API.

🌍 Real-World Impact

  • Controlled for everything where you need live validation, conditional disabling, masking, or transformation.
  • Uncontrolled for huge forms (performance), file inputs, or quick prototypes.

🎯 Likely Interview Questions

  1. Difference between controlled and uncontrolled components?
  2. When would you use one over the other?
  3. Why use defaultValue instead of value in uncontrolled inputs?value makes it controlled and React will keep overwriting user input.
  4. How do you handle file uploads in React? — Always uncontrolled; use ref to read .files.

← useRef | Index | Next: Context API →