Lesson 2 Lab — Typing a Menu Item¶
Practice TypeScript by giving types to the kind of data the app is built from. Same
js-ts-playground scratch project, same src/main.ts — this time you're watching the
type errors, not just the Console. Refer back to the guide for the type map.
Prerequisite: the
js-ts-playgroundproject from Lesson 1, running withnpm run dev, editor open onsrc/main.ts.
Steps¶
- An interface. Define an
IMenuIteminterface with: id: number | undefinedname: stringprice: numbercategoryId: number-
available?: boolean(optional) -
A matching object. Declare
const nachos: IMenuItem = { ... }with real values. Leaveavailableoff — allowed, because it's optional. -
Break it, read it, fix it. Temporarily assign
price: "9.99"(a string). Read the red underline / terminal error, then fix it back to a number. This is the whole point of TypeScript — see it catch the mistake. -
A typed function. Write
function lineTotal(item: IMenuItem, quantity: number): numberthat returnsitem.price * quantity. Call it withnachosand3; log the result. -
Wrong on purpose. Call
lineTotal(nachos, "3")and confirm the compiler rejects the string argument. Fix it. -
A typed array. Declare
const menu: IMenuItem[] = [ ... ]with two or three items. Logmenu.length. -
A literal union. Add
status: "PLACED" | "PREPARING" | "READY"to a smallorderobject — TypeScript's lightweight alternative to an enum. Assign"PLACED"— fine. Now assign"Placed"and confirm the compiler rejects it. Uppercase is what the API actually sends (OrderStatus.Placed = "PLACED"in C#), and every comparison from SQL Server to your components is case-sensitive — so"Placed"is the mistake that compiles, renders, and is silently never true. Never re-case the value; casing is a display choice made at the point of display (<option value="PLACED">Placed</option>). (The reference app typesstatusas plainstring; this literal union is the stricter option — and the one that would have caught this.)
Verify in the browser¶
Verification for this pass is by observation (guide section 9):
- Steps 3, 5, and 7 each produce a type error you can read in the editor and the Vite terminal/Console — then clear once fixed. Catching these is success.
- The valid steps
console.logtheir results with no errors — proof the types compile away to working JavaScript. - Removing the
?fromavailableand re-checkingnachossurfaces a new error (missing required property), showing what optional buys you.
You just described the shape of app data exactly the way every feature folder does — an
IStaff, IProduct, or IRequest interface is the first file you'll write for each
entity, on TableServe in Lesson 3 and on PRS in the capstone.
Stretch challenges¶
Optional — for when you finish early. Not needed for the capstone. [Reinforce] builds on what you just did; [Reach] goes past the guide and needs some research.
- Add a nested type — [Reinforce] — define an
ICategory { id: number; name: string }and addcategory?: ICategorytoIMenuItem. Assign an item both with and without a nested category object. This mirrors the API's nav-property shape. - A generic function — [Reach] — write a
first<T>(items: T[]): T | undefinedthat returns the first element of any typed array (not covered in the guide — research generic functions). Reference: TypeScript: Generics. - A utility type — [Reach] — use
Partial<IMenuItem>to type an object holding just some menu-item fields (the shape an edit form's changes take), and note how it makes every property optional (not in the guide — research it). Reference: TypeScript: Utility Types.
Finished these and want more? See stretch-react-challenges.md for bigger challenges that span the whole React pass.