Loading lesson path
Concept visual
Start at both ends
Type safety for props, state, and context
This tutorial assumes basic knowledge of React. If you're new to React, consider checking out our React Tutorial first.
Formula
Create a new React + TypeScript app with Vite:Example npm create vite@latest my-app -- --template react-ts cd my-app npm install npm run dev Your tsconfig.json should include these recommended compiler options:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Node",Formula
"jsx": "react - jsx","strict": true, "skipLibCheck": true, "noEmit": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true
},
"include": ["src"]
}Keep strict enabled for best type safety. The shown options work well with Vite and Create React App.
Define props with TypeScript and use them in a functional component:
// Greeting.tsx type GreetingProps = {
name: string;
age?: number;
};
export function Greeting({ name, age }: GreetingProps) {
return (<div>
<h2>Hello, {name}!</h2>
{age !== undefined && <p>You are {age} years old</p>}</div>
);
}Type event handlers for inputs and buttons:
// Input change function NameInput() {
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
return <input onChange={handleChange} />;
}
// Button click function SaveButton() {
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
}
return <button onClick={handleClick}>Save</button>;
}Use explicit types for numbers, unions, and nullable values:
const [count, setCount] = React.useState<number>(0);
const [status, setStatus] = React.useState<'idle' | 'loading' | 'error'>('idle');
type User = { id: string; name: string };
const [user, setUser] = React.useState<User | null>(null);useRef with DOM Elements Type refs to DOM nodes to access properties safely:
Example function FocusInput() {
const inputRef = React.useRef<HTMLInputElement>(null);
return <input ref={inputRef} onFocus={() => inputRef.current?.select()} />;
}React.ReactNode type:
Example type CardProps = { title: string; children?: React.ReactNode };
function Card({ title, children }: CardProps) {
return (<div>
<h2>{title}</h2>
{children}</div>
);
}Use generics to type API responses:
Example async function fetchJson<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error('Network error');
return res.json() as Promise<T>;
}
// Usage inside an async function/component effect async function loadPosts() {
type Post = { id: number; title: string };
const posts = await fetchJson<Post[]>("/api/posts");
console.log(posts);
}Provide a small, typed context and a helper hook:
Example type Theme = 'light' | 'dark';
const ThemeContext = React.createContext<{ theme: Theme; toggle(): void } | null>(null);
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = React.useState<Theme>('light');
const value = { theme, toggle: () => setTheme(t => (t === 'light' ? 'dark' : 'light')) };
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
function useTheme() {
const ctx = React.useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}Add Vite's ambient types to avoid missing definitions.
Formula
// src/vite - env.d.ts
/// < reference types ="vite/client" />Alternatively, add to tsconfig.json
{
"compilerOptions": {Formula
"types": ["vite/client"]}
}About React.FC: Prefer directly typed function components. React.FC
is optional; it implicitly adds children but isn't required.These can simplify imports if supported by your bundler.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}Formula
Configure only if your tooling (e.g., Vite, tsconfig - paths) is set up for path aliases.