Building The Order Form
Building a real form in React means thinking in controlled inputs -- here is how a select and a group of radio buttons come together into one Order component.
After the API and proxy setup, the backend is reachable and the linter is happy. The next thing the pizzeria app needs is a way to actually place an order: a form where customers pick a pizza and a size.
This is where React starts feeling different from plain DOM scripting. The form is not just markup -- it is a component that owns its data.
The Order Component
Create src/Order.jsx. The component renders a form with two inputs: a dropdown for pizza type and a group of radio buttons for size.
import { useState } from "react";
const Order = () => {
const [pizzaType, setPizzaType] = useState("pepperoni");
const [pizzaSize, setPizzaSize] = useState("M");
return (
<div className="order">
<h2>Create Order</h2>
<form>
<div>
<label htmlFor="pizza-type">Pizza Type</label>
<select
id="pizza-type"
value={pizzaType}
onChange={(e) => setPizzaType(e.target.value)}
>
<option value="pepperoni">Pepperoni</option>
<option value="hawaiian">Hawaiian</option>
<option value="bbq">BBQ Chicken</option>
</select>
</div>
<div>
<label>Size</label>
{["S", "M", "L"].map((size) => (
<label key={size} htmlFor={`size-${size}`}>
<input
type="radio"
id={`size-${size}`}
name="pizza-size"
value={size}
checked={pizzaSize === size}
onChange={(e) => setPizzaSize(e.target.value)}
/>
{size}
</label>
))}
</div>
<button type="submit">Add to Cart</button>
</form>
</div>
);
};
export default Order;The state lives in the component. pizzaType starts as "pepperoni" and pizzaSize starts as "M". Both change through their respective setter functions.
Controlled Inputs
The key idea here is that the React state is the single source of truth for the form, not the DOM.
On the <select>, the value prop is set to pizzaType. When the user picks a different option, onChange fires and calls setPizzaType. React re-renders with the new value, and the select reflects it. The DOM never leads -- it always follows state.
The same pattern applies to radio buttons. Each one has a checked prop computed by comparing the button's value to pizzaSize. Clicking a radio button fires onChange, sets the new size, and React re-renders all three buttons with the correct one checked.
This is called a controlled input. The alternative (uncontrolled) lets the DOM own the value and you reach in with a ref to read it later. Controlled is almost always the right choice for form state you need to react to.
ExpandOrder form: React state drives the select and radio buttons through controlled inputs
Named Functions Over Inline Arrows for Event Handlers
Inline arrow functions in JSX are convenient:
onChange={(e) => setPizzaType(e.target.value)}But when a component has several event handlers, extracting them to named functions improves the stack trace you get when something breaks:
function handleTypeChange(e) {
setPizzaType(e.target.value);
}
// then:
onChange={handleTypeChange}In a crash report, handleTypeChange tells you exactly where to look. An anonymous arrow function shows up as something like <anonymous> or the component name. The code is also easier to read when the handler has a name that describes what it does.
Using the Component in App
Import Order into App.jsx and drop it into the render tree:
import Order from "./Order";
const App = () => {
return (
<div>
<h1 className="logo">Padre Gino's</h1>
<Order />
</div>
);
};The pizza types are still hardcoded. In the next post the Order component will fetch them from the API using useEffect.
The Essentials
- Controlled inputs mean React state owns the value, not the DOM.
value={state}plusonChange={setter}is the pattern for every select, text input, and textarea. - Radio buttons use
checked={state === thisValue}instead ofvalue. TheonChangehandler is the same. - Named event handlers produce better stack traces than inline arrows and make large components easier to read. Use them when a handler does more than one thing.
htmlForon labels is the JSX equivalent of the HTMLforattribute. Without it, clicking a label does not focus the associated input.
Further Reading and Watching
- Forms in React (React docs) -- the official reference for controlled inputs, including textarea and select
- Controlled vs Uncontrolled Components (Robin Wieruch) -- a clear breakdown of the tradeoffs, including when uncontrolled inputs actually make sense
- HTML Forms Crash Course (Traversy Media) -- covers the underlying HTML form behavior that React's controlled inputs sit on top of