Type-safe components, props, state, hooks, and events. The most common real-world use of TypeScript.
Create a new TypeScript React project with Vite — the fastest way to get started.
# Create a new React + TypeScript project
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev
This gives you a project with .tsx files (TypeScript + JSX), a tsconfig.json, and full type support out of the box.
Define a type or interface for your component's props to get full autocomplete and error checking.
// Define the shape of your props interface ButtonProps { label: string; onClick: () => void; variant?: "primary" | "secondary" | "danger"; disabled?: boolean; } function Button({ label, onClick, variant = "primary", disabled }: ButtonProps) { return ( <button onClick={onClick} disabled={disabled} className={`btn btn-${variant}`} > {label} </button> ); } // Usage — TypeScript will error if you miss required props <Button label="Save" onClick={() => save()} /> // ✅ <Button label="Delete" onClick={() => del()} variant="danger" /> // ✅ // <Button /> ❌ Missing required props: label, onClick
TypeScript usually infers the state type automatically — but sometimes you need to be explicit.
import { useState } from 'react'; interface User { name: string; email: string; } function UserProfile() { // TypeScript infers: number const [count, setCount] = useState(0); // Explicit type — starts as null, will become User const [user, setUser] = useState<User | null>(null); // TypeScript infers: string[] const [items, setItems] = useState<string[]>([]); const loadUser = () => { setUser({ name: "Alice", email: "[email protected]" }); }; return <div>{user?.name ?? "Loading..."}</div>; }
import { useRef } from 'react'; function InputFocus() { // Type the DOM element const inputRef = useRef<HTMLInputElement>(null); const focus = () => { inputRef.current?.focus(); }; return ( <div> <input ref={inputRef} /> <button onClick={focus}> Focus </button> </div> ); }
import { useEffect, useState } from 'react'; interface Post { id: number; title: string; } function Posts() { const [posts, setPosts] = useState<Post[]>([]); useEffect(() => { fetch('/api/posts') .then(r => r.json()) .then((data: Post[]) => { setPosts(data); }); }, []); return <ul>{posts.map(p => <li key={p.id}>{p.title}</li> )}</ul>; }
React exports event types for every HTML event. Use them to type your handlers precisely.
import { ChangeEvent, FormEvent, MouseEvent } from 'react'; function LoginForm() { // Input change event const handleChange = (e: ChangeEvent<HTMLInputElement>) => { console.log(e.target.value); // ✅ TypeScript knows .value exists }; // Form submit event const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); // process form... }; // Button click event const handleClick = (e: MouseEvent<HTMLButtonElement>) => { console.log(e.clientX, e.clientY); }; return ( <form onSubmit={handleSubmit}> <input onChange={handleChange} /> <button onClick={handleClick}>Login</button> </form> ); }
💡 Common React event types: ChangeEvent, FormEvent, MouseEvent, KeyboardEvent, FocusEvent, DragEvent — all from the 'react' package.
import { useState, useEffect } from 'react'; 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()) .then((d: T) => { setData(d); setLoading(false); }) .catch(err => { setError(err.message); setLoading(false); }); }, [url]); return { data, loading, error }; } // Usage — T is inferred from the type annotation interface User { id: number; name: string; } const { data, loading } = useFetch<User>('/api/user/1'); // data is typed as User | null ✅
import { FC } from 'react'; const Card: FC<CardProps> = ({ title }) => { return <div>{title}</div>; }; // Implicitly includes children prop // Not recommended in modern React
interface CardProps { title: string; children?: React.ReactNode; } function Card({ title, children }: CardProps) { return <div>{title}{children}</div>; } // Be explicit about children // Preferred in React 18+
📌 Modern recommendation: Use plain function declarations with typed props interfaces. Avoid React.FC — it's more verbose and no longer provides advantages.