CodingNeed.

2 · Data and boundaries · 30 MIN

Controlled forms and useful validation

A controlled input has a single source of truth in state.

The input’s value comes from state and onChange updates that state for the next render. Submitting the form also works when the learner presses Enter. Validation is shown next to the field and announced through a status message; a label provides an accessible name. Client validation improves feedback but does not authorize or validate an eventual server write. A real endpoint must repeat its own validation and derive ownership from an authenticated session.

Submit with Enter as well as clicking the button.

Read the example

import {useState,type FormEvent} from 'react';
export default function App() {
  const [title,setTitle]=useState(''),[message,setMessage]=useState('');
  function submit(e:FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setMessage(title.trim().length<3?'Use at least three characters.':'Plan created: '+title.trim());
  }
  return <form onSubmit={submit}><h1>Create a study plan</h1><label htmlFor="title">Plan title</label>
    <input id="title" value={title} maxLength={80} aria-describedby="feedback" onChange={e=>setTitle(e.target.value)}/>
    <button>Create plan</button><p id="feedback" role="status">{message}</p></form>;
}
Check the expected output
Submitting fewer than three trimmed characters shows validation. A valid title shows Plan created: followed by the title.

Your challenge

Add a numeric weekly goal with a 1–20 range and announce field-specific errors without clearing valid input.

Solution cost: O(m) trimming and validation for title length m. time · O(m) form text. space

Common trap

Disabled submit buttons alone do not explain what the learner needs to fix.

Study the project implementation
import {useState,type FormEvent} from 'react';
export default function App() {
  const [title,setTitle]=useState(''),[message,setMessage]=useState('');
  function submit(e:FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setMessage(title.trim().length<3?'Use at least three characters.':'Plan created: '+title.trim());
  }
  return <form onSubmit={submit}><h1>Create a study plan</h1><label htmlFor="title">Plan title</label>
    <input id="title" value={title} maxLength={80} aria-describedby="feedback" onChange={e=>setTitle(e.target.value)}/>
    <button>Create plan</button><p id="feedback" role="status">{message}</p></form>;
}

Further reading: Official documentation

Next lesson: Derived state and searchable lists

Essential cookies keep your account signed in. Optional analytics is not configured on this site. Your choice does not affect access to lessons.

Read the Privacy Policy. You can change this choice in the footer.