Stop 04 · Frontend with ReactTopics · 6/8

Forms in React

Part of the Full Stack Developer Roadmap

In short

In React, a form input usually doesn't keep its own value. Your state does, and the input just shows it. That one idea makes validation, resetting and sending to your backend much simpler.

Managing form input as React state (controlled components), validating it, and submitting it to your backend API — one of the most common things a full-stack frontend does.

The mascot dictates to a typist working on an old typewriter outside a court.

Think of it like this

Dictating to the court typist

Outside the district court, you dictate your affidavit and the typist types each word. The paper shows only what the typist typed, never what you merely thought. A controlled input works the same: the screen shows only what your state holds.

Watch it happen

One keystroke to one API call

Input box
React state
API server

onChange: "riya@"

1/6The user types. onChange hands the new text to your setter.

The big ideas

What you need to know

Controlled input

value comes from state and onChange updates state. The input can never drift from your data.

Submit sends state, not DOM

Your submit handler already has every field in state, ready to JSON.stringify and POST.

Show server errors too

Client checks catch typos. Things like 'email taken' only the server knows, so display its error message.

Big forms, use a library

For 10+ fields, React Hook Form with a Zod schema saves a lot of boilerplate and re-renders.

See it in code

Walk through the code

jsx
1export default function SubscribeForm() {2  const [email, setEmail] = useState("");3  const [error, setError] = useState("");4  async function handleSubmit(e) {5    e.preventDefault();6    const res = await fetch("/api/subscribe", { method: "POST",7      headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email }) });8    setError(res.ok ? "" : (await res.json()).error);9    if (res.ok) setEmail("");10  }11  return <form onSubmit={handleSubmit}>12    <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />13    {error && <small role="alert">{error}</small>}14    <button>Subscribe</button>15  </form>;16}

1/4Two pieces of state: what the user typed, and any error from the server.

The input and the request both read from the same email state. One source of truth.

Save yourself the pain

Common mistakes

Oops

Setting value on an input without an onChange, so the user can't type anything.

Do this instead

A controlled input needs both value and onChange. Use defaultValue if you want it uncontrolled.

Oops

Letting the user hit Submit five times while the request is still running.

Do this instead

Keep a submitting flag in state and disable the button until the response comes back.

Your mission

Wire a form to a real endpoint

~35 min

Quick check

You render <input value={name} /> with no onChange. What happens when the user types?

Go deeper

  1. 1Build a form with multiple controlled inputs tracked in a single state object.
  2. 2Submit the form data to a real backend endpoint you've built.
  3. 3Display server-side validation errors returned from your API, not just client-side ones.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge