Controlled Inputs
There are two ways to handle form inputs in React: controlled and uncontrolled. Controlled inputs are the standard approach — React is the single source of truth for the input’s value.
What makes an input controlled
Section titled “What makes an input controlled”An input is controlled when its value is set by React state and its onChange updates that state:
const [name, setName] = useState('');
<input value={name} onChange={e => setName(e.target.value)}/>The input cannot contain anything React did not put there. Every keystroke fires onChange, which calls setName, which triggers a re-render with the new value. The input shows exactly what is in state — always.
Why controlled inputs matter
Section titled “Why controlled inputs matter”With an uncontrolled input, the DOM holds the value. To read it, you use a ref. The problem: the DOM and your state can drift. You might display a total based on old state while the input shows a new value the user typed. Controlled inputs prevent this — the input always reflects what React knows.
ZeroBudget calculates totals from state. If income amounts lived in the DOM instead of state, the totals would be wrong. Every input that feeds a calculation must be controlled.
Clearing inputs
Section titled “Clearing inputs”Because state drives the value, clearing is just setting state:
function handleAdd(e) { e.preventDefault(); if (!name.trim() || !amount) return; addIncome({ name, amount: parseFloat(amount) }); setName(''); setAmount('');}Setting name to '' re-renders the input as empty. No DOM manipulation needed.
Number inputs
Section titled “Number inputs”For numeric inputs, the state value is a string (that is what e.target.value gives you). Convert when you use it:
const [amount, setAmount] = useState('');
// When reading:const total = parseFloat(amount) || 0;
// When saving:addIncome({ amount: parseFloat(amount) });Keeping the state as a string lets the input work naturally — users can type "1." while entering "1.50" without React snapping the value mid-edit.
Select elements
Section titled “Select elements”<select> works the same way:
const [categoryId, setCategoryId] = useState('');
<select value={categoryId} onChange={e => setCategoryId(e.target.value)}> <option value="">Select a category…</option> {categories.map(c => ( <option key={c.id} value={c.id}>{c.name}</option> ))}</select>Checkboxes and radios
Section titled “Checkboxes and radios”Checkboxes use checked instead of value:
const [agreed, setAgreed] = useState(false);
<input type="checkbox" checked={agreed} onChange={e => setAgreed(e.target.checked)}/>Exercise
Section titled “Exercise”- Add controlled
nameandamountinputs toIncomeSection. Both should reflect state and clear on valid submit. - Add a controlled
<select>toTransactionFormthat lists your 8 default categories. - Log the select’s current value to the console on form submit.
- Verify that changing the selection updates state and the select always shows what React put there.
- A controlled input has
valuebound to state andonChangethat updates state. - React is the single source of truth — the input cannot hold values React does not know about.
- Clear inputs by setting state to the empty string.
- Keep number inputs as strings in state; parse with
parseFloatwhen you use the value. - Use
checked(notvalue) for checkboxes and radios.