| name | frontend-reference |
| description | Complete React + TypeScript best practices reference. Covers hooks, component patterns, performance optimization, accessibility, state management, and common anti-patterns with before/after examples. Load this when you need to verify a frontend pattern during code review.
|
Frontend Reference — React + TypeScript Best Practices
Hooks Rules & Patterns
useEffect
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => controller.abort();
}, [fetchData]);
useEffect(() => {
fetchData();
}, []);
const [id, setId] = useState(1);
useEffect(() => {
fetchUser(id);
}, []);
useEffect(() => {
doSomething(options);
}, [options]);
useState vs useMemo
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
setFilteredItems(items.filter(i => i.active));
}, [items]);
const filteredItems = useMemo(
() => items.filter(i => i.active),
[items]
);
Custom Hooks
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
Component Patterns
Single Responsibility
function UserPage() {
}
function UserPage() {
return (
<UserLayout>
<UserProfile />
<UserOrders />
</UserLayout>
);
}
Props Interface
function Button(props: any) { ... }
function Button({ label, onClick }: { label: string; onClick: () => void }) { ... }
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) { ... }
Composition over Configuration
<Card
title="User"
subtitle="Details"
headerRight={<Badge>Active</Badge>}
footer={<Button>Save</Button>}
showDivider
/>
<Card>
<Card.Header>
<Card.Title>User</Card.Title>
<Badge>Active</Badge>
</Card.Header>
<Card.Content>...</Card.Content>
<Card.Footer>
<Button>Save</Button>
</Card.Footer>
</Card>
Key Prop
{items.map((item, index) => <Item key={index} {...item} />)}
{items.map(item => <Item key={item.id} {...item} />)}
TypeScript Patterns
No any
function processData(data: any) { return data.value; }
interface ApiResponse<T> {
data: T;
status: number;
}
function processData<T>(response: ApiResponse<T>): T { return response.data; }
const user = response as any as User;
function isUser(value: unknown): value is User {
return typeof value === 'object' && value !== null && 'id' in value;
}
Discriminated Unions
interface State {
isLoading: boolean;
isError: boolean;
data: User | null;
error: string | null;
}
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; error: string };
Enums vs Const Objects
enum Status { Active = 'active', Inactive = 'inactive' }
const STATUS = { Active: 'active', Inactive: 'inactive' } as const;
type Status = typeof STATUS[keyof typeof STATUS];
Performance Patterns
Memoization
const ExpensiveList = React.memo(function ExpensiveList({ items }: Props) {
return items.map(item => <ExpensiveItem key={item.id} item={item} />);
});
const handleClick = useCallback((id: string) => {
setSelected(id);
}, []);
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
Avoiding Unnecessary Re-renders
<Component style={{ color: 'red' }} />
<Component config={{ theme: 'dark', lang: 'en' }} />
const style = useMemo(() => ({ color: 'red' }), []);
const STYLE = { color: 'red' } as const;
Code Splitting
const HeavyChart = React.lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<Suspense fallback={<Skeleton />}>
<HeavyChart data={data} />
</Suspense>
);
}
Accessibility Quick Reference
Interactive Elements
<button onClick={handleClick}>Save</button>
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
};
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={handleKeyDown} // EXTRACTED function, not inline
>
Save
</div>
Forms
<label htmlFor="email">Email</label>
<input id="email" type="email" aria-describedby="email-hint" />
<p id="email-hint">We'll never share your email.</p>
<span>Email</span>
<input type="email" />
Focus Management
function Modal({ isOpen, onClose }: ModalProps) {
const previousFocus = useRef<HTMLElement | null>(null);
useEffect(() => {
if (isOpen) {
previousFocus.current = document.activeElement as HTMLElement;
}
return () => {
previousFocus.current?.focus();
};
}, [isOpen]);
}
Common Anti-Patterns
| Anti-Pattern | Why It's Bad | Fix |
|---|
useEffect for derived state | Extra render, stale data | useMemo |
useEffect for event handling | Side effects in render | Event handler function |
| Prop drilling 3+ levels | Hard to maintain, rigid | Context, composition, or state lib |
| Index as key | Breaks on reorder/delete | Stable unique ID |
| Inline objects in JSX props | New reference each render | useMemo or constant |
any type | No type safety | Proper types/generics |
| Empty catch blocks | Swallowed errors | Log or handle |
!important in styles | Specificity war | Fix selector specificity |
| Snapshot tests on large components | Brittle, low signal | Behavioral tests |
| Mixing async/sync in effects | Race conditions | Proper async handling with cleanup |