Forms and controlled inputs
In React the value of an input field is usually kept in state. Such a field is called controlled: what is in state is what the field shows, and every keystroke updates the state. The text is always at hand — you can validate it, clear it or send it.
A controlled field
Create src/components/AddTodoForm.jsx:
// src/components/AddTodoForm.jsx
import { useState } from 'react';
export default function AddTodoForm() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={(event) => setText(event.target.value)} />
<p>You typed: {text}</p>
</>
);
}Render <AddTodoForm /> in App. The text under the field changes as you type: value comes from state and onChange updates it.
Submitting the form
onSubmit goes on the form, not on the button — then the Enter key works too. preventDefault() cancels the default page reload:
// src/components/AddTodoForm.jsx
import { useState } from 'react';
export default function AddTodoForm({ onAdd }) {
const [text, setText] = useState('');
function handleSubmit(event) {
event.preventDefault();
onAdd(text);
setText('');
}
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={(event) => setText(event.target.value)} />
<button type="submit">Add</button>
</form>
);
}The form passes the text to its parent through onAdd and clears the field.
Connecting it to the list
In src/App.jsx a task is created from the form text:
// src/App.jsx
function handleAdd(text) {
setTodos([...todos, { id: crypto.randomUUID(), text, done: false }]);
}
// in the markup:
<AddTodoForm onAdd={handleAdd} />
<TodoList todos={todos} onToggle={handleToggle} onDelete={handleDelete} />Type "Water the plants" and press Enter — the task appears at the end of the list and the field empties.
Validating input
The error is computed from state; it needs no state of its own:
// src/components/AddTodoForm.jsx
const trimmed = text.trim();
// in the markup:
<button type="submit" disabled={trimmed === ''}>Add</button>
{text.length > 100 && <p>No more than 100 characters</p>}While the field is empty or holds only spaces, the button is disabled.
Other field types
| Field | Value attribute | What to read in onChange |
|---|---|---|
input, textarea | value | event.target.value |
select | value | event.target.value |
input type="checkbox" | checked | event.target.checked |
In React textarea and select also have a value attribute — there is no need to put text inside the tag or selected on an option.
Common mistake
A field with value but no onChange becomes read-only — you cannot type anything, and the console shows a warning:
// Wrong: the text in the field does not change.
<input value={text} />
// Correct:
<input value={text} onChange={(event) => setText(event.target.value)} />If a field does not need to be controlled, use defaultValue instead of value.