Typing Components
import React from 'react';
// Function component with props interface
interface GreetingProps {
name: string;
age?: number; // optional
}
function Greeting({ name, age }: GreetingProps) {
return (
<div>
Hello, {name}!
{age && <span> You are {age} years old.</span>}
</div>
);
}
// Arrow function component
const Welcome: React.FC<GreetingProps> = ({ name, age }) => {
return <h1>Welcome, {name}!</h1>;
};
// Component with children
interface CardProps {
title: string;
children: React.ReactNode;
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}
// Usage
<Card title="My Card">
<p>Card content</p>
</Card>
Typing Props
// Props with various types
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
icon?: React.ReactNode;
className?: string;
}
function Button({
label,
onClick,
variant = 'primary',
disabled = false,
icon,
className
}: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant} ${className || ''}`}
>
{icon && <span className="icon">{icon}</span>}
{label}
</button>
);
}
// Props extending HTML attributes
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
}
function Input({ label, error, ...inputProps }: InputProps) {
return (
<div>
<label>{label}</label>
<input {...inputProps} />
{error && <span className="error">{error}</span>}
</div>
);
}
// Render props pattern
interface DataFetcherProps<T> {
url: string;
render: (data: T | null, loading: boolean) => React.ReactNode;
}
Typing Hooks
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
// useState with type inference
const [count, setCount] = useState(0); // inferred as number
// useState with explicit type
const [user, setUser] = useState<User | null>(null);
// useState with complex state
interface FormState {
name: string;
email: string;
age: number;
}
const [form, setForm] = useState<FormState>({
name: '',
email: '',
age: 0
});
// useRef
const inputRef = useRef<HTMLInputElement>(null);
const countRef = useRef<number>(0);
// useCallback with typed parameters
const handleClick = useCallback((id: number) => {
console.log('Clicked:', id);
}, []);
// useMemo with return type
const expensiveValue = useMemo<number>(() => {
return someExpensiveCalculation();
}, [dependency]);
// useEffect with cleanup
useEffect(() => {
const handler = (e: KeyboardEvent) => {
console.log(e.key);
};
window.addEventListener('keydown', handler);
return () => {
window.removeEventListener('keydown', handler);
};
}, []);
Custom Hooks
// Custom hook with generics
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = (value: T | ((val: T) => T)) => {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
localStorage.setItem(key, JSON.stringify(valueToStore));
};
return [storedValue, setValue] as const;
}
// Usage
const [name, setName] = useLocalStorage<string>('name', '');
// Fetch hook
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return { data, loading, error };
}
// Usage
const { data: users, loading } = useFetch<User[]>('/api/users');
Event Handling
// Mouse events
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget);
};
// Form events
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// handle form submission
};
// Input events
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
// Select events
const handleSelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
setOption(e.target.value);
};
// Keyboard events
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleSubmit();
}
};
// Focus events
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
e.target.select();
};
// Drag events
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
const files = e.dataTransfer.files;
};
Context API
import { createContext, useContext, useState, ReactNode } from 'react';
// Define context type
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
// Create context with default value
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// Provider component
interface ThemeProviderProps {
children: ReactNode;
}
function ThemeProvider({ children }: ThemeProviderProps) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Custom hook for context
function useTheme(): ThemeContextType {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
// Usage
function ThemedButton() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
Current theme: {theme}
</button>
);
}
Generic Components
// Generic list component
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>
{renderItem(item, index)}
</li>
))}
</ul>
);
}
// Usage
interface User {
id: string;
name: string;
}
const users: User[] = [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' }
];
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => user.id}
/>
Key Takeaways
- • Define interfaces for component props
- • Use generic types for hooks like useState
- • Type event handlers with React event types
- • Create type-safe custom hooks
- • Use generic components for reusability
- • Type Context API with proper null checks
📚 Continue Learning
Apply TypeScript across your full-stack projects:
React Tutorial
Build type-safe React components with hooks, props, and context.
Next.js Tutorial
Full-stack TypeScript with App Router, Server Components, and API routes.
Node.js Tutorial
Build type-safe server-side applications with TypeScript and Node.
Advanced JavaScript
Understand the JS runtime that TypeScript compiles down to.