Module 0.7

TypeScript Quick Reference for React Beginners

2–3 hr · Read + practice

React projects in 2026 are written in TypeScript, not plain JavaScript. TypeScript adds a type system on top of the JavaScript you just learned in the previous tutorial — every variable, every function parameter, every component prop has a type that the compiler checks before your code even runs. It catches whole categories of bugs (typos, missing properties, wrong shapes) before they reach the browser, and it gives your editor the power to autocomplete almost everything.

This tutorial walks you through the TypeScript concepts you’ll see most often in React code. Like the JavaScript tutorial, every section has a hands-on example you can paste into the TypeScript Playground (typescriptlang.org/play) for instant feedback. Don’t just read — type the examples in, hover over the variables to see the inferred types, and try breaking things on purpose to see what the compiler complains about. That’s how you build intuition.


1. Why TypeScript?

In plain JavaScript, this code runs without complaint, but breaks at runtime:

function greet(name) {
  return "Hello, " + name.toUpperCase();
}
greet(42);  // 💥 Runtime error: name.toUpperCase is not a function

In TypeScript, the same code is caught before it ever runs:

function greet(name: string) {
  return "Hello, " + name.toUpperCase();
}
greet(42);  // ❌ Compile error: Argument of type 'number' is not assignable to parameter of type 'string'

The compiler is your first reviewer. It will save you from yourself many times a day.

Why this matters in React: React components have props — values passed in from a parent. TypeScript lets you describe exactly what props a component accepts, and the compiler prevents you from forgetting one or passing the wrong type. Same for state, event handlers, API responses, and everything else you’ll touch.


2. Type Annotations — Variables, Parameters, Return Types

A type annotation is a colon followed by a type, written after a name.

Variables:

const age: number = 25;
const name: string = "Alex";
const isHost: boolean = true;

You can usually omit annotations on variables if the value is right there — TypeScript infers the type. The two lines below are equivalent:

const score = 100;        // inferred as number
const score: number = 100;  // explicit, same effect

Function parameters and return types:

function double(n: number): number {
  return n * 2;
}

Every parameter needs a type (the compiler can’t guess). The return type after the ) is optional — the compiler infers it from the return statement — but writing it explicitly is a good habit on exported functions, because it documents intent.

Arrow functions work the same way:

const triple = (n: number): number => n * 3;

Try in the Playground: type the function below, then call it with the wrong type and watch the editor underline the mistake:

function getInitials(fullName: string): string {
  return fullName.split(" ").map(word => word[0]).join("");
}

console.log(getInitials("Sam Lee"));  // "SL"
console.log(getInitials(42));         // ❌ try this, see the error

Why this matters in React: Every component you write will have parameter types (props) and return types (JSX, which TypeScript expresses as JSX.Element or React.ReactNode).


3. Interfaces and Type Aliases

When you have an object with several fields — like a user, a song, or a component’s props — you describe its shape with either an interface or a type alias.

Interface:

interface Song {
  title: string;
  artist: string;
  durationSeconds: number;
}

const track: Song = {
  title: "Mr. Brightside",
  artist: "The Killers",
  durationSeconds: 222,
};

Type alias (does the same thing, different keyword):

type Song = {
  title: string;
  artist: string;
  durationSeconds: number;
};

Which should you use? For object shapes, they’re nearly interchangeable. The community convention in React projects is: use interface for the shape of component props and public data structures, use type for unions, intersections, and shorthand. Don’t lose sleep over the choice — both are valid.

Optional properties with ?:

interface Song {
  title: string;
  artist: string;
  durationSeconds: number;
  albumArtUrl?: string;  // optional — may or may not be set
}

const track: Song = {
  title: "Mr. Brightside",
  artist: "The Killers",
  durationSeconds: 222,
  // albumArtUrl is fine to omit
};

Readonly properties:

interface Song {
  readonly id: string;  // can't be reassigned after creation
  title: string;
}

Why this matters in React: A component’s props are an object, and you describe that object with an interface:

interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
}

function Button({ label, onClick, disabled }: ButtonProps) {
  return <button onClick={onClick} disabled={disabled}>{label}</button>;
}

Now any caller that forgets label or onClick is rejected by the compiler before they hit save.


4. Union Types, Literal Types, and Narrowing

A union type says “this value is one of several types,” joined with the | pipe character.

let value: string | number;
value = "hello";  // ✅
value = 42;       // ✅
value = true;     // ❌ not in the union

Literal types are unions of specific values, often strings. Hugely common in React for things like agent roles or output formats:

type AgentRole = "planner" | "researcher" | "synthesizer" | "script-writer";

function dispatchAgent(role: AgentRole) {
  console.log(`Dispatching ${role} agent`);
}

dispatchAgent("planner");     // ✅
dispatchAgent("orchestrator"); // ❌ not in the literal union

The compiler’s autocomplete will offer you the four valid values when you type the function call. This is a massive productivity win and a near-magical feature when you first see it.

Narrowing. When a value’s type is a union, you can’t just call any method on it — TypeScript wants you to narrow down which branch you’re in:

function describe(value: string | number): string {
  if (typeof value === "string") {
    // Inside this branch, TypeScript knows value is a string.
    return value.toUpperCase();
  }
  // Outside, it knows value is a number.
  return value.toFixed(2);
}

The typeof check narrows the type. Other narrowing tools include if (value) (truthiness), Array.isArray(), and instanceof. The compiler’s awareness of narrowing is one of the things that makes TypeScript feel smart.

Why this matters in React: Loading states, form values, API responses — they’re all naturally unions. type LoadingState = "idle" | "loading" | "success" | "error" is one of the most common patterns you’ll write.


5. Generics — The <T> Magic

Generics let a function or type work with any type, as long as it’s used consistently. The most important place beginners meet generics is React’s useState hook.

import { useState } from "react";

const [count, setCount] = useState<number>(0);
//                              ^^^^^^^^
//                              This is a generic — the type goes inside <>

Read it as: “useState configured for numbers, initialized to 0.” Now count is typed as number and setCount only accepts number arguments.

setCount(count + 1);  // ✅
setCount("five");     // ❌

For arrays, the most-typed-thing-ever in React is Array<T> (or its shorthand T[]):

const [songs, setSongs] = useState<Array<Song>>([]);
// or equivalently:
const [songs, setSongs] = useState<Song[]>([]);

Both forms mean “a list of Song objects.” Use whichever reads better at the call site; Song[] is more common.

Generic functions — slightly more advanced but you’ll see them:

function firstItem<T>(items: T[]): T | undefined {
  return items[0];
}

const songs: Song[] = [/* ... */];
const top = firstItem(songs);   // T is inferred as Song; top is Song | undefined

The <T> is a placeholder that gets filled in by the actual array’s type at the call site. You almost never have to write generic functions yourself in beginner-level React work, but you’ll use them constantly (every React hook is generic).

Why this matters in React: Every useState, useRef, useReducer, and most third-party hooks (RTK Query especially) take a generic type parameter. Being fluent at reading <T> syntax unlocks understanding of practically every React example you’ll find online.


6. Function and Event Types in React

React event handlers are typed too. The first time you see one, the type looks intimidating; once you know the pattern, it’s everywhere.

function SearchBox() {
  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    console.log(event.target.value);
  }

  return <input onChange={handleChange} />;
}

The pattern:

For different events, the type changes:

EventType
Input changeReact.ChangeEvent<HTMLInputElement>
Form submitReact.FormEvent<HTMLFormElement>
Button clickReact.MouseEvent<HTMLButtonElement>
Key pressReact.KeyboardEvent<HTMLInputElement>

The shortcut: in modern React, you can usually skip the explicit type if you write the handler inline, because TypeScript infers from the JSX context:

return <input onChange={(e) => console.log(e.target.value)} />;
//                       ^ already typed correctly via inference

For function-shaped props on your own components, the simplest way is an arrow type:

interface ButtonProps {
  onClick: () => void;             // takes nothing, returns nothing
  onSelect: (id: string) => void;  // takes a string, returns nothing
}

Why this matters in React: Forms, buttons, dropdowns, drag-and-drop, keyboard shortcuts — every interactive piece of UI involves an event handler with a typed event object. You’ll write hundreds.


7. Utility Types You’ll Use Weekly — Partial, Pick, Omit, Record

TypeScript ships with built-in utility types — generic helpers for transforming other types. Four of them appear so often you should learn them now.

Partial<T> — make every property optional:

interface Song {
  title: string;
  artist: string;
  durationSeconds: number;
}

type SongUpdate = Partial<Song>;
// equivalent to: { title?: string; artist?: string; durationSeconds?: number }

function updateSong(id: string, changes: SongUpdate) { /* ... */ }

updateSong("song-1", { title: "New Title" });  // ✅ only updating one field

Pick<T, K> — keep only the listed keys:

type SongPreview = Pick<Song, "title" | "artist">;
// equivalent to: { title: string; artist: string }

Omit<T, K> — remove the listed keys:

type SongWithoutDuration = Omit<Song, "durationSeconds">;
// equivalent to: { title: string; artist: string }

Record<K, V> — build an object type with keys of one type and values of another:

type AgentColors = Record<AgentRole, string>;
// equivalent to: { planner: string; researcher: string; synthesizer: string; "script-writer": string }

const colors: AgentColors = {
  planner: "#4f46e5",
  researcher: "#10b981",
  synthesizer: "#f59e0b",
  "script-writer": "#ef4444",
};

Why this matters in React: Forms typically need Partial<T> for “what the user has filled in so far.” Card components need Pick<T, ...> for “I only display these three fields.” API responses often need Omit<T, "internal_id"> to drop server-only fields. Record<K, V> builds lookup tables for things like color themes per agent role in your trace UI.


8. How to Read a TypeScript Error

TypeScript’s error messages are intimidating at first but follow a predictable structure. Once you can decode one, you can decode them all.

Example error from VS Code:

Argument of type '{ title: string; }' is not assignable to parameter of type 'Song'.
  Property 'artist' is missing in type '{ title: string; }' but required in type 'Song'.

Read it line by line:

  1. “Argument of type X is not assignable to parameter of type Y” — you tried to pass an X where a Y was expected.
  2. “Property ‘artist’ is missing in type X but required in type Y” — the specific reason. You forgot to include artist.

The fix: add the missing property.

A second common pattern:

Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.

This means you have a value that might be undefined (often from ? optional properties or array access), and you’re trying to use it where a definite string is required. The fix is usually to narrow (check it’s not undefined) before using it.

The discipline: when you see a TypeScript error, read it slowly. The compiler usually tells you exactly what’s wrong and what it expected. With practice, you’ll diagnose 80% of errors in under 10 seconds.

Why this matters: TypeScript errors are not the compiler nagging you. They’re the compiler doing your debugging for you, before runtime, on a free 24/7 plan. Befriend them.


Practice exercises

Paste each of the following into the TypeScript Playground. Predict what the compiler says before you click Run. Then fix the errors.

// 1. Type annotations
function add(a: number, b: number): number {
  return a + b;
}
console.log(add(2, "3"));  // What error appears? Fix it.
// 2. Interfaces
interface Run {
  topic: string;
  status: "planning" | "researching" | "synthesizing" | "complete";
  startedAt: Date;
}

const myRun: Run = {
  topic: "AI safety in 2026",
  status: "researching",
};
// What's missing? Add it.
// 3. Generics
import { useState } from "react";  // pretend this works in the Playground
const [count, setCount] = useState<number>(0);
setCount("five");  // Why is this wrong?
// 4. Utility types
interface Song {
  title: string;
  artist: string;
  durationSeconds: number;
  albumArtUrl: string;
}

type SongPreview = Pick<Song, "title" | "artist">;
const preview: SongPreview = {
  title: "Mr. Brightside",
  artist: "The Killers",
  durationSeconds: 222,  // Why is this rejected?
};
// 5. Reading errors
function getFirstChar(s: string): string {
  return s[0];
}
const result = getFirstChar(undefined);  // What error appears?

Cheat sheet (keep this open while you code)

ConceptSyntax
Variable annotationconst x: number = 5;
Function annotationfunction f(x: number): string { ... }
Interfaceinterface Song { title: string; }
Type aliastype Song = { title: string; };
Optional propertyinterface X { prop?: number; }
Readonly propertyinterface X { readonly id: string; }
Union typetype Status = "idle" | "loading" | "done";
Generic stateuseState<number>(0)
Generic arraySong[] or Array<Song>
Function type(x: string) => boolean
Partial<T>every prop optional
Pick<T, K>keep only listed keys
Omit<T, K>remove listed keys
Record<K, V>object with K-typed keys, V-typed values

What’s next

You’ve now seen the TypeScript surface area you’ll meet in 90% of React code. The deep parts (conditional types, mapped types, template literal types) are real but rare and you’ll learn them on the job if you ever need them.

If you want one more hands-on practice round, work through chapters 1–4 of the official TypeScript Handbook (typescriptlang.org/docs/handbook/). Otherwise: see you on Day 1.

Happy coding!