React TypeScript Cheatsheet
Basic Component Types
Function Components
// Basic Function Component
const Button: React.FC<{
onClick: () => void;
children: React.ReactNode;
}> = ({ onClick, children }) => (
<button onClick={onClick}>{children}</button>
);
// Alternative syntax (preferred)
interface ButtonProps {
onClick: () => void;
children: React.ReactNode;
}
function Button({ onClick, children }: ButtonProps) {
return <button onClick={onClick}>{children}</button>;
}
Props Types
// Basic props
interface UserProps {
name: string;
age: number;
email?: string; // Optional prop
children?: React.ReactNode; // Children prop
}
// Props with functions
interface ButtonProps {
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
onHover?: (event: React.MouseEvent<HTMLButtonElement>) => void;
}
// Props with generics
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
Event Handlers
// Common Event Types
interface InputProps {
// Mouse events
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
onMouseOver: (event: React.MouseEvent<HTMLDivElement>) => void;
// Keyboard events
onKeyPress: (event: React.KeyboardEvent<HTMLInputElement>) => void;
// Form events
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
// Focus events
onFocus: (event: React.FocusEvent<HTMLInputElement>) => void;
onBlur: (event: React.FocusEvent<HTMLInputElement>) => void;
}
// Event handler implementations
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
};
Hooks with TypeScript
useState
// Basic types
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>('');
const [isLoading, setIsLoading] = useState<boolean>(false);
// Complex types
interface User {
id: number;
name: string;
email: string;
}
const [user, setUser] = useState<User | null>(null);
// Array state
const [items, setItems] = useState<string[]>([]);
useRef
// DOM refs
const inputRef = useRef<HTMLInputElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
// Mutable refs
const countRef = useRef<number>(0);
const timerRef = useRef<NodeJS.Timeout>();
useEffect
// Basic effect
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
// Cleanup function
useEffect(() => {
const handler = (event: MouseEvent) => {
console.log(event.clientX, event.clientY);
};
window.addEventListener('mousemove', handler);
return () => {
window.removeEventListener('mousemove', handler);
};
}, []);
useReducer
interface State {
count: number;
error: string | null;
loading: boolean;
}
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'SET_ERROR'; payload: string }
| { type: 'SET_LOADING'; payload: boolean };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_ERROR':
return { ...state, error: action.payload };
case 'SET_LOADING':
return { ...state, loading: action.payload };
default:
return state;
}
};
// Usage
const [state, dispatch] = useReducer(reducer, {
count: 0,
error: null,
loading: false
});
Context with TypeScript
// Create context with type
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// Provider component
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
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
export const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
Generic Components
// Generic list component
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}
// Usage
interface User {
id: number;
name: string;
}
<List<User>
items={users}
renderItem={(user) => <span>{user.name}</span>}
/>
Utility Types
// Partial - Makes all properties optional
type PartialUser = Partial<User>;
// Required - Makes all properties required
type RequiredUser = Required<User>;
// Pick - Selects specific properties
type NameAndEmail = Pick<User, 'name' | 'email'>;
// Omit - Removes specific properties
type UserWithoutId = Omit<User, 'id'>;
// Record - Creates an object type with specific key and value types
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;
// Readonly - Makes all properties readonly
type ReadonlyUser = Readonly<User>;
Type Assertions and Guards
// Type assertions
const input = event.target as HTMLInputElement;
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
// Type guards
function isUser(obj: any): obj is User {
return 'id' in obj && 'name' in obj && 'email' in obj;
}
// Usage with type guard
if (isUser(data)) {
console.log(data.name); // TypeScript knows data is User
}
Best Practices
-
Props Types:
- Use interfaces for props
- Make props readonly when possible
- Use discriminated unions for complex props
-
Event Handlers:
- Use the most specific event type possible
- Provide proper types for event.target
-
State Management:
- Define explicit types for state
- Use discriminated unions for actions
- Leverage TypeScript's inference when possible
-
Performance:
- Use proper dependency types in useCallback/useMemo
- Avoid unnecessary type assertions
- Utilize const assertions where appropriate