The Bug That Convinced Me
I shipped a feature. QA tested it. Looked good. Went to production.
Five minutes later: “App is crashing for 20% of users.”
The bug? I typed user.adress instead of user.address. A simple typo. Took 30 seconds to fix, took 3 hours of firefighting to deploy.
TypeScript would have caught that in 0.5 seconds. That’s when I converted.
Why TypeScript with React?
Without TypeScript:
function UserProfile({ user, onUpdate }) {
// What properties does user have?
// What does onUpdate expect?
// ¯\_(ツ)_/¯
return <div>{user.name}</div>;
}
With TypeScript:
interface User {
id: string;
name: string;
email: string;
}
interface Props {
user: User;
onUpdate: (user: User) => Promise<void>;
}
function UserProfile({ user, onUpdate }: Props) {
return <div>{user.name}</div>;
}
Benefits:
- Autocomplete shows you all available properties
- Refactoring is safe (rename a property, TypeScript finds all usages)
- Bugs caught at compile time, not runtime
- Self-documenting code
Getting Started
# New project with Vite (faster)
npm create vite@latest my-app -- --template react-ts
Typing Components
// Direct typing (recommended)
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
function Button({ label, onClick, variant = 'primary', disabled }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled} className={variant}>
{label}
</button>
);
}
// Children
interface ContainerProps {
children: React.ReactNode;
className?: string;
}
// Event handlers
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
Typing Hooks
// useState
const [count, setCount] = useState(0); // inferred
const [user, setUser] = useState<User | null>(null); // explicit
// useRef
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
// Custom hooks
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((data: T) => {
setData(data);
setLoading(false);
})
.catch((err: Error) => {
setError(err);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
Advanced: Discriminated Unions
// Instead of boolean flags
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; error: Error };
function UserProfile() {
const [state, setState] = useState<State>({ status: 'idle' });
switch (state.status) {
case 'idle':
return <button onClick={fetchUser}>Load User</button>;
case 'loading':
return <div>Loading...</div>;
case 'success':
return <div>{state.data.name}</div>; // TypeScript knows data exists
case 'error':
return <div>Error: {state.error.message}</div>;
}
}
Utility Types
interface User {
id: string;
name: string;
email: string;
password: string;
}
type PublicUser = Omit<User, 'password'>;
type PartialUser = Partial<User>; // All optional
type RequiredUser = Required<PartialUser>; // All required
type UserRoles = Record<'admin' | 'user', boolean>;
Common Mistakes
1. Using any Everywhere
// DON'T
function handleData(data: any) {
console.log(data.name);
}
// DO
interface Data {
name: string;
}
function handleData(data: Data) {
console.log(data.name);
}
2. Not Typing API Responses
// DON'T
const data = await fetch('/api/user').then(res => res.json());
// DO
interface ApiResponse {
user: User;
token: string;
}
const data: ApiResponse = await fetch('/api/user').then(res => res.json());
Quick Tips
- Start with
strict: true- Pain now, pleasure later - Use type inference - Don’t over-type
- Create types for API responses
- Use discriminated unions over boolean flags
- Avoid
any- Useunknownif you don’t know the type - Type component props - Even if simple
TypeScript feels like overkill until it saves you from a production bug at 2 AM.
Start small. Add types gradually. Your future self will thank you.
Happy typing!