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.

May 15, 20265 min read1 / 7

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.

JSX
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.

Order form: React state drives the select and radio buttons through controlled inputs 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:

JSX
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:

JSX
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:

JSX
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

  1. Controlled inputs mean React state owns the value, not the DOM. value={state} plus onChange={setter} is the pattern for every select, text input, and textarea.
  2. Radio buttons use checked={state === thisValue} instead of value. The onChange handler is the same.
  3. 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.
  4. htmlFor on labels is the JSX equivalent of the HTML for attribute. Without it, clicking a label does not focus the associated input.

Further Reading and Watching