TypeScript + React — Advanced Patterns
TypeScript transforms React development by catching errors at compile time. Advanced patterns — discriminated unions for component variants, generics for reusable hooks, exhaustive type checking, and conditional types — make components self-documenting and refactoring safe.
TypeScript Patterns for React
// ---- 1. Discriminated unions for component variants ----
type ButtonVariant =
| { variant: 'primary'; onClick: () => void }
| { variant: 'link'; href: string; external?: boolean }
| { variant: 'submit'; form?: string }; // associated with a form
type ButtonProps = ButtonVariant & {
children: React.ReactNode;
disabled?: boolean;
size?: 'sm' | 'md' | 'lg';
};
function Button(props: ButtonProps) {
if (props.variant === 'link') {
return (
<a href={props.href} target={props.external ? '_blank' : undefined} rel="noopener noreferrer">
{props.children}
</a>
);
}
// TS knows: props.onClick is available here (primary or submit)
const type = props.variant === 'submit' ? 'submit' : 'button';
return <button type={type} onClick={props.variant === 'primary' ? props.onClick : undefined}>{props.children}</button>;
}
// Exhaustive check — catch missing union members at compile time
function assertNever(x: never): never {
throw new Error('Unexpected value: ' + x);
}
function getButtonStyles(variant: 'primary' | 'secondary' | 'danger'): string {
switch (variant) {
case 'primary': return 'bg-blue-600 text-white';
case 'secondary': return 'bg-gray-200 text-gray-900';
case 'danger': return 'bg-red-600 text-white';
default: return assertNever(variant); // TS error if variant is missing
}
}
// ---- 2. Generic hooks with constrained types ----
// Generic useList hook — works with any item type that has an 'id'
function useList<T extends { id: string }>(initialItems: T[]) {
const [items, setItems] = useState<T[]>(initialItems);
function add(item: T) { setItems(prev => [...prev, item]); }
function remove(id: string) { setItems(prev => prev.filter(i => i.id !== id)); }
function update(id: string, changes: Partial<Omit<T, 'id'>>) {
setItems(prev => prev.map(i => i.id === id ? { ...i, ...changes } : i));
}
function reorder(fromIndex: number, toIndex: number) {
setItems(prev => {
const next = [...prev];
const [moved] = next.splice(fromIndex, 1);
next.splice(toIndex, 0, moved);
return next;
});
}
return { items, add, remove, update, reorder };
}
// Usage — fully typed based on the item type
const { items: users, add, remove } = useList<User>([]);
remove('123'); // ✅ TypeScript knows remove takes a string
add({ id: '1', name: 'Alice', role: 'admin' }); // ✅ must match User shape
// ---- 3. Conditional props ----
// If 'label' is provided, 'htmlFor' is required
type LabelProps = {
label: string;
htmlFor: string; // required when label is present
} | {
label?: never;
htmlFor?: never;
};
type InputProps = React.InputHTMLAttributes<HTMLInputElement> & LabelProps;
// ---- 4. Readonly — prevent prop mutation ----
interface Config {
readonly apiUrl: string;
readonly features: Readonly<{
darkMode: boolean;
analytics: boolean;
}>;
}
// ---- 5. Template literal types ----
type EventName = `on${Capitalize<string>}`; // onSubmit, onClick, onChange...
type CSSProperty = `${string}-${string}`; // border-radius, font-weight...
type ApiRoute = `/api/${'users' | 'products' | 'orders'}`; // only valid routesTip
Tip
Practice TypeScript React Advanced Patterns in small, isolated examples before integrating into larger projects. Breaking concepts into small experiments builds genuine understanding faster than reading alone.
TypeScript = JavaScript + static types → fewer bugs, better tooling
Practice Task
Note
Practice Task — (1) Write a working example of TypeScript React Advanced Patterns from scratch without looking at notes. (2) Modify it to handle an edge case (empty input, null value, or error state). (3) Share your solution in the Priygop community for feedback.
Quick Quiz
Common Mistake
Warning
A common mistake with TypeScript React Advanced Patterns is skipping edge case testing — empty inputs, null values, and unexpected data types. Always validate boundary conditions to write robust, production-ready react code.