TypeScript Tips for React Developers
Introduction
TypeScript has become an essential tool in modern React development, providing type safety and improved developer experience. This guide covers essential TypeScript patterns and best practices specifically for React applications.
Type-Safe Props
Basic Props Interface
Start with properly typing your component props:
interface UserCardProps {
name: string;
email: string;
age?: number; // Optional prop
onProfileClick: (userId: string) => void;
}
const UserCard: React.FC<UserCardProps> = ({
name,
email,
age,
onProfileClick,
}) => {
return (
<div onClick={() => onProfileClick(name)}>
<h2>{name}</h2>
<p>{email}</p>
{age && <p>Age: {age}</p>}
</div>
);
};Generic Components
Create reusable components with generics:
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>
);
}State Management with TypeScript
useState with Type Inference
interface User {
id: string;
name: string;
email: string;
}
const [user, setUser] = useState<User | null>(null);useReducer with Discriminated Unions
type Action =
| { type: "SET_LOADING" }
| { type: "SET_DATA"; payload: string[] }
| { type: "SET_ERROR"; payload: string };
interface State {
loading: boolean;
data: string[] | null;
error: string | null;
}
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "SET_LOADING":
return { ...state, loading: true };
case "SET_DATA":
return { ...state, loading: false, data: action.payload };
case "SET_ERROR":
return { ...state, loading: false, error: action.payload };
}
};Custom Hooks
Type-Safe Custom Hooks
interface UseApiResponse<T> {
data: T | null;
loading: boolean;
error: string | null;
}
function useApi<T>(url: string): UseApiResponse<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchData();
}, [url]);
const fetchData = async () => {
try {
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return { data, loading, error };
}Event Handling
Typing Event Handlers
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log(event.target.value);
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
// Form submission logic
};Best Practices
Use Type Assertions Sparingly
Only use type assertions when you know more about the type than TypeScript does:
// Avoid
const element = document.getElementById("root") as HTMLElement;
// Better
const element = document.getElementById("root");
if (element === null) throw new Error("Root element not found");Leverage Utility Types
TypeScript provides several utility types that are helpful in React development:
// Make all properties optional
type PartialUser = Partial<User>;
// Make all properties required
type RequiredUser = Required<User>;
// Pick specific properties
type UserNameAndEmail = Pick<User, "name" | "email">;
// Omit specific properties
type UserWithoutId = Omit<User, "id">;Conclusion
Using TypeScript with React not only provides better type safety but also improves the development experience through better tooling and documentation. By following these patterns and best practices, you can write more maintainable and robust React applications.
Indra Arianggi