interface UserCardProps {
name: string;
role: string;
isOnline?: boolean;
}
function UserCard({ name, role, isOnline = false }: UserCardProps) {
return (
<div>
<h3>{name}</h3>
<p>{role}</p>
<p>{isOnline ? "๐ข Online" : "โซ Offline"}</p>
</div>
);
}
<UserCard name="Meera" role={42} /> // โ TypeScript error - role must be a stringThis is the direct TypeScript upgrade of Chapter 8's Props chapter โ instead of discovering a wrong prop type as a confusing bug at runtime, your editor flags it immediately while you're still typing the JSX.
import { useState, ChangeEvent, FormEvent } from "react";
interface FormData {
email: string;
age: number;
}
function SignupForm() {
const [form, setForm] = useState<FormData>({ email: "", age: 0 });
function handleChange(e: ChangeEvent<HTMLInputElement>) {
setForm({ ...form, [e.target.name]: e.target.value });
}
function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
console.log(form);
}
return <form onSubmit={handleSubmit}><input name="email" onChange={handleChange} /></form>;
}useState<FormData> tells TypeScript exactly what shape the state object has, catching any mismatched update. ChangeEvent<HTMLInputElement> and FormEvent<HTMLFormElement> give you fully typed access to e.target.value and e.preventDefault() from Chapters 11 and 14.
import { useRef } from "react";
function SearchBox() {
const inputRef = useRef<HTMLInputElement>(null);
return <input ref={inputRef} />;
}
// Typing a fetched API response (from Chapter 21)
interface User {
id: number;
name: string;
}
async function getUsers(): Promise<User[]> {
const res = await fetch("/api/users");
return res.json();
}useRef<HTMLInputElement>(null) tells TypeScript exactly which native DOM methods are available on inputRef.current (like .focus()). Typing your API's response shape means every place you use that fetched data gets full autocomplete and error-checking, catching typos in field names (like user.naem) immediately.
interface AuthContextType {
username: string | null;
login: (name: string) => void;
}
const AuthContext = createContext<AuthContextType | null>(null);
function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error("useAuth must be used inside AuthProvider");
return context;
}This pattern โ a typed Context plus a custom hook (combining Chapters 24 and 29) that throws a clear error if used outside its Provider โ is the standard, professional way to type Context in real TypeScript + React projects.
Writing function UserCard(props: any) compiles, but throws away every benefit TypeScript offers for that component โ no autocomplete, no error checking on what's passed in. Always define a real interface for a component's props, even a very simple one; it takes seconds and pays for itself the first time you catch a typo.
Define a typed Props interface for a ProductCard component (name: string, price: number, inStock: boolean) and use it to type the component.
interface ProductCardProps {
name: string;
price: number;
inStock: boolean;
}
function ProductCard({ name, price, inStock }: ProductCardProps) {
return (
<div>
<h3>{name}</h3>
<p>${price}</p>
<p>{inStock ? "In Stock" : "Sold Out"}</p>
</div>
);
}
Q What's the difference between a .jsx file and a .tsx file?
A .tsx file is a TypeScript file that's allowed to contain JSX syntax (component markup) โ it's compiled the same way, but TypeScript type-checks everything in it. Plain .ts files can't contain JSX at all.
Q Do I need to type every single useState call?
Not always โ TypeScript can often infer the type automatically from the initial value, e.g. useState(0) is automatically typed as a number. Explicit typing (useState