Settings
Your save file

Progress lives in this browser, on this device. Download the save file to move it to another device, or to keep a copy in case this browser clears its storage.

Season 2
Week 7 of 12

Week 7 · Jury Phase

The Veto Ceremony

Typing React

Props interfaces, useState<T>, event handlers, children. Your daily world, typed.

Six weeks of syntax on functions nobody was going to page you about at 2am. This week is different. This is props interfaces, event handlers, children, the stuff sitting in every component you will actually touch on the BBJ rebuild. Everything before this was practice for this.

A props object is just an interface

You already know interface from week two. A component's props are an object, same as any other object, which means they get the same treatment: name the shape, list each prop with its type, then destructure that shape straight in the function signature.

The ES6 you write

function NomineeBadge(props) {
  return (
    <span onClick={props.onSelect}>
      {props.name}
    </span>
  )
}

The TS version

interface NomineeBadgeProps {
  name: string
  onSelect: (e: MouseEvent<HTMLSpanElement>) => void
}
 
function NomineeBadge({ name, onSelect }: NomineeBadgeProps) {
  return (
    <span onClick={onSelect}>
      {name}
    </span>
  )
}

What changed

props goes from an untyped grab bag, anything could be sitting inside props.name or props.onSelect and TypeScript would never notice, to a named interface, NomineeBadgeProps, destructured directly in the parameter list. onSelect's type, (e: MouseEvent<HTMLSpanElement>) => void, tells the compiler and every caller exactly what shape of handler is expected here, not just some function.

Nothing about the JSX changed. What changed is that props.name used to be whatever anyone felt like passing, and now it is provably a string, at the keyboard, before the component ever renders once.

useState<T>, and when the generic actually matters

useState almost always infers its type fine on its own: useState(0) is number, useState('') is string, no generic needed. The generic earns its keep in one very common situation: state that starts out null or undefined but will hold something else later. useState(null) alone infers the type null, forever, which means TypeScript will refuse to ever let that state become anything but null. Tell it the real shape up front instead:

const [selectedHouseguest, setSelectedHouseguest] = useState<string | null>(null)

Now the state starts at null, can later hold a string, and every place you read selectedHouseguest has to handle both, honestly, because the type says both are possible.

Event handler types, the ones you will actually type

Every DOM event handler prop has a matching generic event type, parameterized by which element it is attached to. You will reach for these constantly enough that memorizing the shape beats looking it up every time:

| JSX prop | Handler type | | --- | --- | | onClick | (e: MouseEvent<HTMLButtonElement>) => void | | onChange | (e: ChangeEvent<HTMLInputElement>) => void | | onSubmit | (e: FormEvent<HTMLFormElement>) => void |

The element type in the angle brackets changes with what you actually attached the handler to, HTMLButtonElement, HTMLInputElement, HTMLSpanElement, whatever tag owns the prop. Get it right and e.target.value on a change handler is a known string, no guessing, no as cast to convince the compiler your input actually has a .value.

children, typed

Any component that wraps other JSX needs a type for whatever gets passed between its tags. That type is ReactNode, imported from react, and it covers everything JSX is willing to render: elements, strings, numbers, arrays of those, null, all of it.

function Panel({ children }: { children: ReactNode }) {
  return <div className="panel">{children}</div>
}

No separate interface needed for a component that only takes children, an inline object type does the job, same as it would for any single-property parameter.

That is the whole lesson. Now prove it in the comp below.

Up next

Week 8 · The Feeds Leak

Typing API data

Locked until you win