TypeScript Tips for React Developers
TypeScript has become the standard for building large-scale React applications. Here are some patterns I’ve found invaluable in my daily work.
Typing Component Props
Basic Props Interface
interface ButtonProps {
variant: "primary" | "secondary" | "outline";
size?: "sm" | "md" | "lg";
children: React.ReactNode;
onClick?: () => void;
}
function Button({ variant, size = "md", children, onClick }: ButtonProps) {
return (
<button className={`btn btn-${variant} btn-${size}`} onClick={onClick}>
{children}
</button>
);
}
Extending HTML Element Props
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
}
function Input({ label, error, ...props }: InputProps) {
return (
<div>
<label>{label}</label>
<input {...props} />
{error && <span className="error">{error}</span>}
</div>
);
}
Generic Components
Create flexible, reusable components with generics:
interface SelectProps<T> {
options: T[];
value: T;
onChange: (value: T) => void;
getLabel: (option: T) => string;
getValue: (option: T) => string;
}
function Select<T>({
options,
value,
onChange,
getLabel,
getValue,
}: SelectProps<T>) {
return (
<select
value={getValue(value)}
onChange={(e) => {
const selected = options.find(
(opt) => getValue(opt) === e.target.value
);
if (selected) onChange(selected);
}}
>
{options.map((option) => (
<option key={getValue(option)} value={getValue(option)}>
{getLabel(option)}
</option>
))}
</select>
);
}
Discriminated Unions for State
Handle complex state with discriminated unions:
type AsyncState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };
function useAsync<T>(): [AsyncState<T>, (promise: Promise<T>) => void] {
const [state, setState] = useState<AsyncState<T>>({ status: "idle" });
const execute = async (promise: Promise<T>) => {
setState({ status: "loading" });
try {
const data = await promise;
setState({ status: "success", data });
} catch (error) {
setState({ status: "error", error: error as Error });
}
};
return [state, execute];
}
Utility Types You Should Know
Partial<T>- Makes all properties optionalRequired<T>- Makes all properties requiredPick<T, K>- Select specific propertiesOmit<T, K>- Remove specific propertiesRecord<K, V>- Create an object type with specific keys
Conclusion
TypeScript adds a learning curve, but the benefits in code quality and developer experience are substantial. Start with these patterns and gradually adopt more advanced techniques as you become comfortable.