TypeScript in React
Typing React components, props, state, and events

Pixl presents
Untyped props are how a component quietly gets a number where it wanted a string. Type your props, state and events.Untyped props are how a component quietly gets a number where it wanted a string. Type your props, state and events.

Ever passed the wrong prop to a React component and then burned 20 minutes staring at a blank screen wondering why nothing shows up? Yeah... TypeScript is about to be your new best friend.
It catches prop mistakes the second you make them, autocompletes the props a component takes while you type, and tells you what each component needs without you digging through its source code.
Typing Component Props#
The thing you'll do MOST with TypeScript in React is type your component's props.
// Define what props the component accepts
interface GreetingProps {
name: string;
age: number;
isVIP?: boolean; // Optional prop
}
// Use the interface to type the component
function Greeting({ name, age, isVIP }: GreetingProps) {
return (
<div>
<h1>Hello, {name}!</h1>
<p>Age: {age}</p>
{isVIP && <span>VIP Member</span>}
</div>
);
}
// TypeScript checks props when you use the component
<Greeting name="Alice" age={28} /> // OK
<Greeting name="Bob" age="thirty" /> // Error! string not number
<Greeting name="Charlie" /> // Error! missing 'age'Common Prop Types#
These are the prop types you'll reach for most.
interface ComponentProps {
// Basic types
title: string;
count: number;
isActive: boolean;
// Arrays and objects
items: string[];
user: { name: string; email: string };
// Functions
onClick: () => void; // No args, no return
onChange: (value: string) => void; // One arg, no return
onSubmit: (data: FormData) => Promise<void>; // Async
// React-specific types
children: React.ReactNode; // Any renderable content
style?: React.CSSProperties; // Inline styles
className?: string; // CSS classes
// Union types for variants
variant: "primary" | "secondary" | "danger";
size: "sm" | "md" | "lg";
}Children Props#
The children prop lets a component wrap around other content.
interface CardProps {
title: string;
children: React.ReactNode;
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// Usage - children is whatever goes between the tags
<Card title="Welcome">
<p>This is the card content.</p>
<button>Click me</button>
</Card>Typing State with useState#
TypeScript can usually guess your state types, but every now and then you have to spell it out.
import { useState } from 'react';
function UserProfile() {
// TypeScript infers these automatically
const [name, setName] = useState("Alice"); // string
const [count, setCount] = useState(0); // number
const [isOpen, setIsOpen] = useState(false); // boolean
// Explicit type needed when initial value doesn't match future values
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);
const [error, setError] = useState<string | undefined>(undefined);
// Later...
setUser({ name: "Alice", email: "[email protected]" }); // OK
setItems(["apple", "banana"]); // OK
}
interface User {
name: string;
email: string;
}Typing Events#
React events have their own specific types. Here are the ones you'll see the most.
function FormExample() {
// Input change event
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
// Form submit event (older code uses React.FormEvent here, which current React types mark as deprecated)
const handleSubmit = (e: React.SubmitEvent<HTMLFormElement>) => {
e.preventDefault();
// Process form data
};
// Button click event
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log("Button clicked at", e.clientX, e.clientY);
};
// Keyboard event
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
console.log("Enter pressed!");
}
};
return (
<form onSubmit={handleSubmit}>
<input
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
<button onClick={handleClick}>Submit</button>
</form>
);
}Generic Components#

You can build components that work with any kind of data.
// A generic list component
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}
// Usage - TypeScript infers the type from items
interface User {
id: number;
name: string;
}
<List
items={[{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]}
renderItem={(user) => <span>{user.name}</span>}
/>
// TypeScript knows 'user' is User in renderItemTyping Refs#
Refs need a type parameter for whichever DOM element they point at.
import { useRef } from 'react';
function InputFocus() {
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
// Need to check if ref is attached
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} placeholder="Click button to focus" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}Extending HTML Element Props#
Sometimes you want a component that takes every prop a normal HTML element takes, plus a few of your own.
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant: "primary" | "secondary";
isLoading?: boolean;
}
function Button({ variant, isLoading, children, ...rest }: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
disabled={isLoading}
{...rest}
>
{isLoading ? "Loading..." : children}
</button>
);
}
// Accepts all normal button props plus your custom ones
<Button variant="primary" onClick={() => {}} type="submit">
Save
</Button>TL;DR#
- Describe component props with interfaces, like
interface Props { name: string } - Use
React.ReactNodeas the type for thechildrenprop useStatefigures out types by itself, but it needs an explicit type when the starting value is null- Event handlers have specific types, like
React.ChangeEvent<HTMLInputElement> - Generic components (
<T>) give you reusable, type-safe building blocks - Extend HTML attributes to make souped-up versions of native elements
What's Next?#
And that's the TypeScript module, donezo! You've covered types, interfaces, generics, union types, utility types, project setup, and React. That's a LOT (look at you go).
Real talk though, reading about it only gets you so far. Three weeks of poking at this stuff won't make you an expert (and anybody claiming expert status after three weeks of Claude Code deserves a raised eyebrow). Practice is what makes it stick. Try converting a small JavaScript project to TypeScript, or start your next project in TypeScript from day one...
This lesson ends with a short activity.
