Typescript for react developers
As a React developer, you already understand component-based architecture, state management, and JSX. TypeScript enhances your React development by adding static type checking, better tooling, and improved code quality. Let’s focus on the TypeScript concepts most relevant to React.
Basic TypeScript Types for React
Section titled “Basic TypeScript Types for React”Primitive Types
Section titled “Primitive Types”const name: string = "John";const age: number = 30;const isActive: boolean = true;const items: string[] = ["item1", "item2"];Union Types
Section titled “Union Types”type Status = "loading" | "success" | "error";const status: Status = "loading";Typing React Components
Section titled “Typing React Components”Functional Components
Section titled “Functional Components”// Basic component with propstype Props = { name: string; age?: number; // Optional prop};
//If no age param setting default to 18const Greeting = ({ name, age = 18 }: Prop) => { return ( <div> Hello {name} {age && <span>, you are {age} years old</span>} </div> );};Alternative: Using React.FC vs Direct Function
Section titled “Alternative: Using React.FC vs Direct Function”// With React.FC (includes children by default)const Component: React.FC<Props> = ({ children }) => <div>{children}</div>;
// Without React.FC (more explicit)const Component = ({ children }: Props & { children?: React.ReactNode }) => ( <div>{children}</div>);Common React Patterns with TypeScript
Section titled “Common React Patterns with TypeScript”useState Hook
Section titled “useState Hook”import { useState } from "react";
const UserProfile = () => { // Type inferred: string const [name, setName] = useState("John");
// Explicit type for complex state const [user, setUser] = useState<{ name: string; age: number } | null>(null);
// Array state with type const [todos, setTodos] = useState<string[]>([]);};useEffect and useCallback
Section titled “useEffect and useCallback”import { useEffect, useCallback } from "react";
interface User { id: number; name: string;}
const UserComponent = ({ userId }: { userId: number }) => { const [user, setUser] = useState<User | null>(null);
// Typed callback const fetchUser = useCallback(async (id: number): Promise<User> => { const response = await fetch(`/api/users/${id}`); return response.json(); }, []);
useEffect(() => { fetchUser(userId).then(setUser); }, [userId, fetchUser]);};useReducer Hook
Section titled “useReducer Hook”type State = { count: number;};
type Action = | { type: "increment" } | { type: "decrement" } | { type: "reset"; payload: number };
const reducer = (state: State, action: Action): State => { switch (action.type) { case "increment": return { count: state.count + 1 }; case "decrement": return { count: state.count - 1 }; case "reset": return { count: action.payload }; default: return state; }};
const Counter = () => { const [state, dispatch] = useReducer(reducer, { count: 0 });
return ( <div> Count: {state.count} <button onClick={() => dispatch({ type: "increment" })}>+</button> </div> );};Event Handling
Section titled “Event Handling”Form Events
Section titled “Form Events”const LoginForm = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); // Handle form submission };
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => { setEmail(e.target.value); };
return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={handleEmailChange} /> <input type="password" value={password} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value) } /> <button type="submit">Login</button> </form> );};Click Events
Section titled “Click Events”const Button = ({ onClick }: { onClick: (e: React.MouseEvent) => void }) => { return <button onClick={onClick}>Click me</button>;};Advanced Component Patterns
Section titled “Advanced Component Patterns”Generic Components
Section titled “Generic Components”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<List<{ id: number; name: string }> items={users} renderItem={(user) => <span>{user.name}</span>}/>;Higher-Order Components
Section titled “Higher-Order Components”interface WithLoadingProps { loading: boolean;}
const withLoading = <P extends object>( Component: React.ComponentType<P>): React.FC<P & WithLoadingProps> => { return ({ loading, ...props }: WithLoadingProps & P) => { if (loading) return <div>Loading...</div>; return <Component {...(props as P)} />; };};
// Usageconst UserProfile = ({ user }: { user: User }) => <div>{user.name}</div>;const UserProfileWithLoading = withLoading(UserProfile);Context API with TypeScript
Section titled “Context API with TypeScript”interface ThemeContextType { theme: "light" | "dark"; toggleTheme: () => void;}
const ThemeContext = React.createContext<ThemeContextType | undefined>( undefined);
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 contextconst useTheme = () => { const context = useContext(ThemeContext); if (context === undefined) { throw new Error("useTheme must be used within a ThemeProvider"); } return context;};Custom Hooks with TypeScript
Section titled “Custom Hooks with TypeScript”interface UseLocalStorageResult<T> { value: T; setValue: (value: T) => void;}
function useLocalStorage<T>( key: string, initialValue: T): UseLocalStorageResult<T> { const [value, setValue] = useState<T>(() => { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : initialValue; });
useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]);
return { value, setValue };}
// Usageconst { value: user, setValue: setUser } = useLocalStorage<User>( "user", initialUser);Common Utility Types
Section titled “Common Utility Types”// Partial for optional props in formsinterface UserForm { name: string; email: string; age: number;}
const updateUser = (updates: Partial<UserForm>) => { // Only some fields required};
// Pick and Omit for specific propstype UserName = Pick<User, "name">;type UserWithoutId = Omit<User, "id">;
// Record for object mapsconst userRoles: Record<number, string> = { 1: "admin", 2: "user",};Best Practices
Section titled “Best Practices”- Start with interfaces for props and state
- Use type aliases for union types and complex types
- Leverage type inference when possible
- Create custom hooks with proper return types
- Use generic components for reusable logic
- Always type event handlers
- Use optional chaining and nullish coalescing with TypeScript
Common Errors and Solutions
Section titled “Common Errors and Solutions”// Error: Object is possibly 'null'const user = userMaybeNull?.name; // ✅ Use optional chaining
// Error: Type is not assignableconst safeUser = user as User; // ✅ Use type assertion sparingly
// Better: Type guardsfunction isUser(obj: any): obj is User { return obj && typeof obj.name === "string";}
if (isUser(someObject)) { // someObject is now typed as User}