Good to knowReact Fundamentals · 05

TypeScript with React

Typing props like any function: literal unions, deriving types with typeof/keyof, satisfies, and the errors tsc reports.

On this page · 7 sections
  1. In one minute
  2. The example: a Price that knows its currencies
  3. How it works
  4. Common mistakes
  5. Interview Q&A
  6. Related
  7. Sources

In one minute

A component is a function, so typing it is typing a function’s parameter: describe the props object. React adds only a few types of its own (ReactNode, ComponentProps<'span'>, event types). The rest is everyday TypeScript, and a few techniques make it much stronger: literal unions instead of string, deriving types from values with typeof/keyof, and satisfies to check an object without losing its exact keys.

You’ll be able to: type a component’s props, keep one source of truth for allowed values, and read the errors tsc gives you.

price.tsx
1const formatters = {2  USD: (cents: number) => `$${cents / 100}`,3  EUR: (cents: number) => `€${cents / 100}`,4  GBP: (cents: number) => `£${cents / 100}`,5} satisfies Record<string, Formatter>67type Currency = keyof typeof formatters89type PriceProps = { cents: number; currency?: Currency }1011<Price cents="89.00" />      // ✗12<Price currency="JPY" />     // ✗13<Price currency="EUR" />     // ✗
What you see
Currency = ?
tsc: not run yet
  1. } satisfies Record<string, Formatter>satisfies checks that every value is a (cents: number) => string, but does not widen the object: its keys stay exactly USD, EUR, GBP.
  2. type Currency = keyof typeof formatterstypeof turns the value into its type, and keyof takes its keys as a union. Add a currency to the object and the type follows.
  3. type PriceProps = { cents: number; currency?: Currency }The props use the derived union.
  4. <Price cents="89.00" /> // ✗Three mistakes, three compile errors, before any code runs: a string for a number (TS2322), an unknown currency (TS2322), and a missing cents (TS2741).

0 / 0
One source of truth: the object defines the allowed currencies, and the types follow it.

The example: a Price that knows its currencies

type Formatter = (cents: number) => string

const formatters = {
	USD: (cents: number) => `$${(cents / 100).toFixed(2)}`,
	EUR: (cents: number) => `€${(cents / 100).toFixed(2)}`,
	GBP: (cents: number) => `£${(cents / 100).toFixed(2)}`,
} satisfies Record<string, Formatter>

export type Currency = keyof typeof formatters // 'USD' | 'EUR' | 'GBP'
type PriceProps = {
	cents: number
	currency?: Currency
}

export function Price({ cents, currency = 'USD' }: PriceProps) {
	return <p className="price">{formatters[currency](cents)}</p>
}

Three mistakes the compiler catches (the lesson’s 05-typescript.bad.tsx):

// Mistakes TypeScript catches before the code runs (not part of the app build).
import { Price } from './05-typescript'

export const examples = (
	<>
		<Price cents="89.00" />
		<Price cents={8900} currency="JPY" />
		<Price currency="EUR" />
	</>
)

What tsc --noEmit reported:

05-typescript.bad.tsx(6,10): error TS2322: Type 'string' is not assignable to type 'number'.
05-typescript.bad.tsx(7,23): error TS2322: Type '"JPY"' is not assignable to type '"USD" | "EUR" | "GBP" | undefined'.
05-typescript.bad.tsx(8,4): error TS2741: Property 'cents' is missing in type '{ currency: "EUR"; }' but required in type 'PriceProps'.

None of these would have failed loudly at runtime: "89.00" / 100 quietly becomes 0.89, and formatters["JPY"] is undefined, which crashes only when that price is shown.

How it works

Common mistakes

Interview Q&A

Should you type components with React.FC?

Usually not. Type the props parameter directly: it covers everything React.FC does, works naturally for generic components (function List<T>(props: ListProps<T>)), and doesn’t imply props you didn’t declare.

Why a union of string literals instead of string?

string accepts any value, including ones nothing handles. A union like 'USD' | 'EUR' | 'GBP' turns a runtime bug into a compile error: the recorded tsc output rejects currency="JPY" with TS2322.

What do typeof and keyof do when combined?

In a type position, typeof formatters gives the type of the runtime value; keyof then gives the union of its keys. keyof typeof formatters is 'USD' | 'EUR' | 'GBP', derived from the object so there’s one source of truth.

What does satisfies give you that an annotation doesn't?

An annotation (const x: T = …) makes the variable’s type exactly T, losing detail such as the exact keys. satisfies T checks the value against T but keeps the value’s own inferred type, so keyof typeof formatters still lists the three currencies.

How do you type a component that wraps a native element?

Intersect the element’s props with your own: ComponentProps<'span'> & { tone?: Tone }, destructure what you use, and spread the rest onto the element.

Sources

Primary source: react.dev/learn/typescript