React with TypeScript
TypeScript adds static type checking to JavaScript. Using it with React catches bugs at compile time, powers autocomplete in your editor, and makes refactoring safe.
Setup
# New project
npm create vite@latest my-app -- --template react-ts
# Add to existing Vite project
npm install -D typescript @types/react @types/react-dom
Typing Props
// Option 1: inline interface
function UserCard({ name, age, role, onDelete }: {
name: string;
age?: number; // optional prop
role: "admin" | "user" | "moderator"; // union type
onDelete: (id: string) => void;
}) {
return <div>{name}</div>;
}
// Option 2: separate interface (preferred for reuse)
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
tags?: string[];
}
function ProductCard({ product }: { product: Product }) {
return <div>{product.name} โ ${product.price}</div>;
}
Typing useState
// Type inferred from initial value
const [count, setCount] = useState(0); // number
const [name, setName] = useState(""); // string
const [isOpen, setIsOpen] = useState(false); // boolean
// Explicit type needed when initial value is null or empty
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<Product[]>([]);
// useRef
const inputRef = useRef<HTMLInputElement>(null);
const timerRef = useRef<number | null>(null);
Typing useEffect & Events
// Events are typed automatically
function SearchInput() {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") search();
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} onKeyDown={handleKeyDown} />
</form>
);
}
Typing Custom Hooks
interface FetchResult<T> {
data: T | null;
loading: boolean;
error: string | null;
}
function useFetch<T>(url: string): FetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then(r => r.json() as Promise<T>)
.then(setData)
.catch((e: Error) => setError(e.message))
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
// Usage โ fully typed
const { data: users } = useFetch<User[]>("/api/users");
// users is User[] | null โ TypeScript knows the shape
TipStart new projects with TypeScript from day one. Adding it to an existing project later is painful. The
react-ts Vite template sets everything up for you instantly.