Engrain by ByteRush

RCT.10.01 Mid-level· 12 minCore — asked often

Form approaches in React: controlled, uncontrolled, Actions and React Hook Form

Choose how to build forms: controlled vs uncontrolled inputs, native forms with React 19 Actions, React Hook Form, one schema (Zod) for client and server validation, field arrays, and dirty and touched state.

Comes up in: React, Machine coding

Learn#

Pooja’s 40-field insurance form lags on a mid-range phone: every keystroke re-renders the whole form. And her server accepts emails the client rejected, because the rules were written twice and drifted.

A controlled input stores its value in React state and updates on every keystroke; easy to read and change, but it re-renders. An uncontrolled input keeps its value in the DOM, and you read it on submit, which is fast for big forms. React 19 Actions (RCT.05.01) and React Hook Form both build on uncontrolled inputs.

Schema validation writes the rules once and uses them in both places, the browser and the server:

import { z } from "zod";

// One schema, used by the form in the browser AND by the Server Action: rules are written once.
const Signup = z.object({
  name: z.string().trim().min(2, "Enter your name"),
  email: z.string().email("Enter an email like name@example.com"),
  pin: z.string().regex(/^\d{6}$/, "Enter the 6-digit PIN code"),
});

const result = Signup.safeParse({ name: "A", email: "asha@", pin: "4110" });
if (!result.success) console.log(z.flattenError(result.error).fieldErrors);
console.log(Signup.parse({ name: "  Asha ", email: "asha@example.in", pin: "411001" }));
This example uses Node.js, so it runs in our checks rather than in your browser.

Output (verified)

{
  name: [ 'Enter your name' ],
  email: [ 'Enter an email like name@example.com' ],
  pin: [ 'Enter the 6-digit PIN code' ]
}
{ name: 'Asha', email: 'asha@example.in', pin: '411001' }
Uncontrolled inputs, one schema in two placesStep 1 of 4

State

  • keystroke → render Form (40 fields)

Page

    empty

    Controlled form: every keystroke calls setState, so the whole 40-field form re-renders on each key.

    Read the steps as text
    1. Controlled form: every keystroke calls setState, so the whole 40-field form re-renders on each key.
    2. Uncontrolled form (native or React Hook Form): the DOM keeps the value; React reads it on submit or blur.
    3. On submit, one schema checks the values in the browser and shows field errors at once.
    4. The same schema runs again in the Server Action, because the browser check can be skipped.

    Predict#

    Predict first

    Client-side validation passes. Can you skip validating the same data in the Server Action?

    Interview angle#

    Say it like this — about 30 seconds

    “Controlled inputs keep values in React state: simple, and good for instant formatting or dependent fields, but each keystroke re-renders. Uncontrolled inputs keep values in the DOM, which scales to large forms; React Hook Form and React 19 form Actions build on that. I define validation once with a schema like Zod, run it in the browser for quick feedback and again on the server for safety, and infer the TypeScript type from it. Errors are tied to fields with aria-describedby and aria-invalid.”

    Stays on your device. Nothing is uploaded.
    Follow-up Controlled vs uncontrolled forms: performance trade-offs?
    Controlled re-renders on every keystroke (fine for small forms); uncontrolled avoids that, which matters for big forms and slow phones.
    Follow-up How do you validate forms on client and server without duplicating rules?
    One schema in a shared module, used by the form resolver and by the server handler.
    Follow-up How do you make form errors accessible?
    Text next to each field linked with aria-describedby, aria-invalid on the input, and focus or a summary on submit (RCT.10.02).

    Practice#

    Guided

    Build the form state a form library keeps: values, dirty and touched fields, and a field array.

    Done when

    • Dirty fields compare against the initial values, including arrays.
    • Touched fields are recorded once, in order.
    • Appending and removing items never mutates the old array, and reset restores everything.
    Your solution
    Ctrl/⌘ + Enter

    Remember#

    Try to answer each card before you open it. Then add them to your reviews.

    Controlled vs uncontrolled input?

    Controlled: value in React state. Uncontrolled: value in the DOM, read when needed.

    How do you avoid writing validation rules twice?

    One schema shared by the client form and the server handler.

    What does dirty mean for a field?

    Its value differs from the initial value.

    Why validate on the server even after client validation?

    Requests can bypass the form.

    Go deeper

    Four layers. Stop at the one your interview needs; each ends with a quick check.

    The core ideaEvery round

    Use controlled inputs when the UI reacts to each keystroke: formatting a card number, enabling fields based on others, live search. Otherwise uncontrolled scales better.

    Check yourself: When is a controlled input the right choice?

    When the UI must react to each keystroke, such as formatting or live dependent fields.

    How it works underneathSenior rounds
    React Hook Form registers inputs through refs and reads values from the DOM, so typing does not re-render the form; components subscribe only to the state they need Source: React Hook Form: useForm.
    With React 19, a plain form action plus useActionState handles many forms with no library.
    Check yourself: How does React Hook Form avoid re-renders?

    It registers inputs with refs and reads values from the DOM, re-rendering only what subscribes.

    Edge cases and trapsWhere people slip
    A field array is a repeated group of fields, such as addresses. Give each item a stable id for its key, not its index (RCT.02.01).
    ”Dirty” means changed from the initial value; “touched” means the user left the field, a good time to show its error.
    Check yourself: What is a field array?

    A list of repeated field groups, such as several addresses, that users can add and remove.

    In productionStaff-level answers

    Form state belongs to the form, not global stores. Show errors after blur or submit, not while typing the first character Source: Zod: Formatting errors.

    Check yourself: Which Zod helper groups errors by field?

    z.flattenError(error).fieldErrors.

    Finished the five steps? Mark it, and its cards join your reviews.

    Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

    Interview questions this answers

    • Controlled vs uncontrolled forms: performance trade-offs?
    • How do you validate forms on client and server without duplicating rules?
    • How do you make form errors accessible?