
React Code Review
- 2 installs
- 318 repo stars
- Updated June 22, 2026
- giuseppe-trisciuoglio/developer-kit-claude-code
This is a copy of react-code-review by giuseppe-trisciuoglio - installs and ranking accrue to the original listing.
Helps with frontend development tasks.
About
react-code-review is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.
- react-code-review
- Frontend Development
- AI-coding skill
React Code Review by the numbers
- 2 all-time installs (skills.sh)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit-claude-code --skill react-code-reviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 318 |
| Last updated | June 22, 2026 |
| Repository | giuseppe-trisciuoglio/developer-kit-claude-code ↗ |
What it does
Helps with frontend development tasks.
Files
React Code Review
Overview
This skill provides structured, comprehensive code review for React applications. It evaluates code against React 19 best practices, component architecture patterns, hook usage, accessibility standards, and production-readiness criteria. The review produces actionable findings categorized by severity (Critical, Warning, Suggestion) with concrete code examples for improvements.
This skill delegates to the react-software-architect-review agent for deep architectural analysis when invoked through the agent system.
When to Use
- Reviewing React components, hooks, and pages before merging
- Validating component composition and reusability patterns
- Checking proper hook usage (useState, useEffect, useMemo, useCallback)
- Reviewing React 19 patterns (use, useOptimistic, useFormStatus, Actions)
- Evaluating state management approaches (local, context, external stores)
- Assessing performance optimization (memoization, code splitting, lazy loading)
- Reviewing accessibility compliance (WCAG, semantic HTML, ARIA)
- Validating TypeScript typing for props, state, and events
- Checking Tailwind CSS and styling patterns
- After implementing new React features or refactoring component architecture
Instructions
1. Identify Scope: Determine which React components and hooks are under review. Use glob to discover .tsx/.jsx files and grep to identify component definitions, hook usage, and context providers.
2. Analyze Component Architecture: Verify proper component composition — check for single responsibility, appropriate size, and reusability. Look for components that are too large (>200 lines), have too many props (>7), or mix concerns.
3. Review Hook Usage: Validate proper hook usage — check dependency arrays in useEffect/useMemo/useCallback, verify cleanup functions in useEffect, and identify unnecessary re-renders caused by missing or incorrect memoization.
4. Evaluate State Management: Assess where state lives — check for proper colocation, unnecessary lifting, and appropriate use of Context vs external stores. Verify that server state uses TanStack Query, SWR, or similar libraries rather than manual useEffect + useState patterns.
5. Check Accessibility: Review semantic HTML usage, ARIA attributes, keyboard navigation, focus management, and screen reader compatibility. Verify that interactive elements are accessible and form inputs have proper labels.
6. Assess Performance: Look for unnecessary re-renders, missing React.memo on expensive components, improper use of useCallback/useMemo, missing code splitting, and large bundle imports.
7. Review TypeScript Integration: Check prop type definitions, event handler typing, generic component patterns, and proper use of utility types. Verify that any is not used where specific types are possible.
8. Produce Review Report: Generate a structured report with severity-classified findings (Critical, Warning, Suggestion), positive observations, and prioritized recommendations with code examples.
Examples
Example 1: Hook Dependency Issues
// ❌ Bad: Missing dependency causes stale closure
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // Missing userId in dependency array
return <div>{user?.name}</div>;
}
// ✅ Good: Proper dependencies with cleanup
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
let cancelled = false;
fetchUser(userId).then((data) => {
if (!cancelled) setUser(data);
});
return () => { cancelled = true; };
}, [userId]);
return <div>{user?.name}</div>;
}
// ✅ Better: Use TanStack Query for server state
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
if (isLoading) return <Skeleton />;
return <div>{user?.name}</div>;
}Example 2: Component Composition
// ❌ Bad: Monolithic component mixing data fetching, filtering, and rendering
function Dashboard() {
const [users, setUsers] = useState([]);
const [filter, setFilter] = useState('');
useEffect(() => { /* fetch + filter + sort all in one */ }, [filter]);
return <div>{/* 200+ lines of mixed concerns */}</div>;
}
// ✅ Good: Composed from focused components with custom hooks
function Dashboard() {
return (
<div>
<UserFilters />
<Suspense fallback={<TableSkeleton />}>
<UserTable />
</Suspense>
<UserPagination />
</div>
);
}Example 3: Accessibility Review
// ❌ Bad: Inaccessible interactive elements
function Menu({ items }: { items: MenuItem[] }) {
const [open, setOpen] = useState(false);
return (
<div>
<div onClick={() => setOpen(!open)}>Menu</div>
{open && (
<div>
{items.map(item => (
<div key={item.id} onClick={() => navigate(item.path)}>
{item.label}
</div>
))}
</div>
)}
</div>
);
}
// ✅ Good: Accessible with proper semantics and keyboard support
function Menu({ items }: { items: MenuItem[] }) {
const [open, setOpen] = useState(false);
return (
<nav aria-label="Main navigation">
<button
onClick={() => setOpen(!open)}
aria-expanded={open}
aria-controls="menu-list"
>
Menu
</button>
{open && (
<ul id="menu-list" role="menu">
{items.map(item => (
<li key={item.id} role="menuitem">
<a href={item.path}>{item.label}</a>
</li>
))}
</ul>
)}
</nav>
);
}Example 4: Performance Optimization
// ❌ Bad: Unstable callback recreated every render causes child re-renders
{filtered.map(product => (
<ProductCard
key={product.id}
product={product}
onSelect={() => console.log(product.id)} // New function each render
/>
))}
// ✅ Good: Stable callback + memoized child
const handleSelect = useCallback((id: string) => {
console.log(id);
}, []);
const filtered = useMemo(
() => products.filter(p => p.name.toLowerCase().includes(search.toLowerCase())),
[products, search]
);
{filtered.map(product => (
<ProductCard key={product.id} product={product} onSelect={handleSelect} />
))}
const ProductCard = memo(function ProductCard({ product, onSelect }: Props) {
return <div onClick={() => onSelect(product.id)}>{product.name}</div>;
});Example 5: TypeScript Props Review
// ❌ Bad: Loose typing and missing prop definitions
function Card({ data, onClick, children, ...rest }: any) {
return (
<div onClick={onClick} {...rest}>
<h2>{data.title}</h2>
{children}
</div>
);
}
// ✅ Good: Strict typing with proper interfaces
interface CardProps extends React.ComponentPropsWithoutRef<'article'> {
title: string;
description?: string;
variant?: 'default' | 'outlined' | 'elevated';
onAction?: (event: React.MouseEvent<HTMLButtonElement>) => void;
children: React.ReactNode;
}
function Card({
title,
description,
variant = 'default',
onAction,
children,
className,
...rest
}: CardProps) {
return (
<article className={cn('card', `card--${variant}`, className)} {...rest}>
<h2>{title}</h2>
{description && <p>{description}</p>}
{children}
{onAction && <button onClick={onAction}>Action</button>}
</article>
);
}Review Output Format
Structure all code review findings as follows:
1. Summary
Brief overview with an overall quality score (1-10) and key observations.
2. Critical Issues (Must Fix)
Issues causing bugs, security vulnerabilities, or broken functionality.
3. Warnings (Should Fix)
Issues that violate best practices, cause performance problems, or reduce maintainability.
4. Suggestions (Consider Improving)
Improvements for code organization, accessibility, or developer experience.
5. Positive Observations
Well-implemented patterns and good practices to acknowledge.
6. Recommendations
Prioritized next steps with code examples for the most impactful improvements.
Best Practices
- Keep components focused — single responsibility, under 200 lines
- Colocate state with the components that use it
- Use custom hooks to extract reusable logic from components
- Apply
React.memoonly when measured re-render cost justifies it - Use TanStack Query or SWR for server state instead of
useEffect+useState - Always include cleanup functions in
useEffectwhen subscribing to external resources - Write semantic HTML first, add ARIA only when native semantics are insufficient
- Use TypeScript strict mode and avoid
anyin component props - Implement error boundaries for graceful failure handling
- Prefer composition over conditional rendering complexity
Constraints and Warnings
- Respect the project's React version — avoid suggesting React 19 features for older versions
- Do not enforce a specific state management library unless the project has standardized on one
- Memoization is not always beneficial — only suggest it when re-render impact is measurable
- Accessibility recommendations should follow WCAG 2.1 AA as the baseline
- Focus on high-confidence issues — avoid false positives on subjective style choices
- Do not suggest rewriting working components without clear, measurable benefit
References
See the references/ directory for detailed review checklists and pattern documentation:
references/hooks-patterns.md— React hooks best practices and common mistakesreferences/component-architecture.md— Component composition and design patternsreferences/accessibility.md— Accessibility checklist and ARIA patterns for React
React Accessibility Checklist and ARIA Patterns
Semantic HTML First
Always use the correct semantic element before reaching for ARIA attributes.
| Need | Use | Not |
|---|---|---|
| Button | <button> | <div onClick> |
| Link/navigation | <a href> | <span onClick> |
| List | <ul> / <ol> | <div> with items |
| Heading | <h1>–<h6> | <div className="title"> |
| Navigation | <nav> | <div className="nav"> |
| Main content | <main> | <div className="content"> |
| Form input label | <label htmlFor> | Placeholder text only |
| Table data | <table> | <div> grid layout |
Interactive Elements
Buttons vs Links
- Button: Triggers an action (submit, toggle, open modal)
- Link: Navigates to a URL or page section
// ✅ Correct: Button for action
<button onClick={handleSave} type="button">Save</button>
// ✅ Correct: Link for navigation
<a href="/settings">Go to Settings</a>
// ❌ Wrong: Div as button
<div onClick={handleSave} className="btn">Save</div>
// ❌ Wrong: Button for navigation
<button onClick={() => router.push('/settings')}>Go to Settings</button>Keyboard Navigation
All interactive elements must be keyboard accessible.
// ✅ Custom interactive element with keyboard support
function ToggleSwitch({ checked, onChange }: ToggleSwitchProps) {
return (
<button
role="switch"
aria-checked={checked}
onClick={() => onChange(!checked)}
onKeyDown={(e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
onChange(!checked);
}
}}
>
{checked ? 'On' : 'Off'}
</button>
);
}Focus Management
Modal Focus Trap
'use client';
import { useEffect, useRef } from 'react';
function Modal({ isOpen, onClose, children }: ModalProps) {
const modalRef = useRef<HTMLDivElement>(null);
const previousFocus = useRef<HTMLElement | null>(null);
useEffect(() => {
if (isOpen) {
previousFocus.current = document.activeElement as HTMLElement;
modalRef.current?.focus();
} else {
previousFocus.current?.focus();
}
}, [isOpen]);
if (!isOpen) return null;
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
ref={modalRef}
tabIndex={-1}
onKeyDown={(e) => {
if (e.key === 'Escape') onClose();
}}
>
<h2 id="modal-title">Modal Title</h2>
{children}
<button onClick={onClose}>Close</button>
</div>
);
}Skip Navigation Link
Allow keyboard users to skip repetitive navigation.
function SkipToMain() {
return (
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:p-4 focus:bg-white"
>
Skip to main content
</a>
);
}
function Layout({ children }: { children: ReactNode }) {
return (
<>
<SkipToMain />
<Header />
<main id="main-content" tabIndex={-1}>
{children}
</main>
</>
);
}ARIA Patterns
Live Regions
Announce dynamic content changes to screen readers.
// ✅ Announce form submission status
function ContactForm() {
const [status, setStatus] = useState('');
return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
<button type="submit">Send</button>
<div role="status" aria-live="polite" aria-atomic="true">
{status}
</div>
</form>
);
}Expandable Sections
function Accordion({ title, children }: AccordionProps) {
const [expanded, setExpanded] = useState(false);
const contentId = useId();
return (
<div>
<button
aria-expanded={expanded}
aria-controls={contentId}
onClick={() => setExpanded(!expanded)}
>
{title}
</button>
<div
id={contentId}
role="region"
hidden={!expanded}
>
{children}
</div>
</div>
);
}Tabs Pattern
function Tabs({ tabs }: { tabs: TabData[] }) {
const [activeTab, setActiveTab] = useState(0);
return (
<div>
<div role="tablist" aria-label="Content tabs">
{tabs.map((tab, index) => (
<button
key={tab.id}
role="tab"
id={`tab-${tab.id}`}
aria-selected={activeTab === index}
aria-controls={`panel-${tab.id}`}
tabIndex={activeTab === index ? 0 : -1}
onClick={() => setActiveTab(index)}
onKeyDown={(e) => {
if (e.key === 'ArrowRight') setActiveTab((activeTab + 1) % tabs.length);
if (e.key === 'ArrowLeft') setActiveTab((activeTab - 1 + tabs.length) % tabs.length);
}}
>
{tab.label}
</button>
))}
</div>
{tabs.map((tab, index) => (
<div
key={tab.id}
role="tabpanel"
id={`panel-${tab.id}`}
aria-labelledby={`tab-${tab.id}`}
hidden={activeTab !== index}
>
{tab.content}
</div>
))}
</div>
);
}Forms Accessibility
Label Association
Every form input must have an associated label.
// ✅ Explicit label with htmlFor
<label htmlFor="email">Email address</label>
<input id="email" type="email" aria-required="true" />
// ✅ Wrapping label
<label>
Email address
<input type="email" aria-required="true" />
</label>
// ❌ No label — only placeholder
<input type="email" placeholder="Email" />Error Messages
function FormField({ label, error, ...props }: FormFieldProps) {
const id = useId();
const errorId = `${id}-error`;
return (
<div>
<label htmlFor={id}>{label}</label>
<input
id={id}
aria-invalid={!!error}
aria-describedby={error ? errorId : undefined}
{...props}
/>
{error && (
<p id={errorId} role="alert" className="text-red-500">
{error}
</p>
)}
</div>
);
}Color and Contrast
- Minimum contrast ratio: 4.5:1 for normal text, 3:1 for large text (WCAG AA)
- Don't convey information through color alone — use icons, patterns, or text
- Support
prefers-color-schemefor dark mode - Test with color blindness simulators
Motion and Animation
// Respect user motion preferences
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// In Tailwind CSS
<div className="transition-transform motion-reduce:transition-none">
Animated content
</div>
// In CSS
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}Review Checklist
Structure
- [ ] Semantic HTML elements used (nav, main, article, section, aside)
- [ ] Heading hierarchy is logical (h1 → h2 → h3, no skipping)
- [ ] Landmarks present (header, nav, main, footer)
- [ ] Skip navigation link provided
- [ ] Page has a descriptive
<title>
Interactive Elements
- [ ] All interactive elements are keyboard accessible
- [ ] Focus order is logical (follows visual order)
- [ ] Focus indicators are visible
- [ ] No keyboard traps
- [ ] Custom interactive elements have proper ARIA roles
Forms
- [ ] All inputs have associated labels
- [ ] Required fields indicated with
aria-required - [ ] Error messages linked with
aria-describedby - [ ] Error states use
aria-invalid - [ ] Form groups use
fieldsetandlegend
Images and Media
- [ ] All images have
alttext (emptyalt=""for decorative) - [ ] Complex images have extended descriptions
- [ ] Videos have captions and transcripts
- [ ] Audio has transcripts
Dynamic Content
- [ ] Live regions announce important changes
- [ ] Loading states communicated to screen readers
- [ ] Modals trap focus and restore on close
- [ ] Toast notifications use
role="status"orrole="alert"
React Component Architecture Patterns
Component Composition
Container / Presentational Pattern
Separate data logic from visual rendering.
// Container: Handles data and state
function UserListContainer() {
const { data: users, isLoading } = useUsers();
const [filter, setFilter] = useState('');
if (isLoading) return <UserListSkeleton />;
return <UserList users={users} filter={filter} onFilterChange={setFilter} />;
}
// Presentational: Pure rendering, easy to test
function UserList({
users,
filter,
onFilterChange,
}: {
users: User[];
filter: string;
onFilterChange: (value: string) => void;
}) {
const filtered = users.filter(u => u.name.includes(filter));
return (
<div>
<input value={filter} onChange={e => onFilterChange(e.target.value)} />
{filtered.map(user => <UserCard key={user.id} user={user} />)}
</div>
);
}Compound Components
Components that work together sharing implicit state.
// Compound component API
<Tabs defaultValue="overview">
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="overview">Overview content</Tabs.Content>
<Tabs.Content value="settings">Settings content</Tabs.Content>
</Tabs>
// Implementation using Context
const TabsContext = createContext<TabsState | null>(null);
function Tabs({ defaultValue, children }: TabsProps) {
const [value, setValue] = useState(defaultValue);
return (
<TabsContext.Provider value={{ value, setValue }}>
<div role="tablist">{children}</div>
</TabsContext.Provider>
);
}
Tabs.List = function TabsList({ children }: { children: ReactNode }) {
return <div role="tablist">{children}</div>;
};
Tabs.Trigger = function TabsTrigger({ value, children }: TabsTriggerProps) {
const ctx = useContext(TabsContext)!;
return (
<button
role="tab"
aria-selected={ctx.value === value}
onClick={() => ctx.setValue(value)}
>
{children}
</button>
);
};
Tabs.Content = function TabsContent({ value, children }: TabsContentProps) {
const ctx = useContext(TabsContext)!;
if (ctx.value !== value) return null;
return <div role="tabpanel">{children}</div>;
};Polymorphic Components
Components that render as different HTML elements.
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
children: React.ReactNode;
} & Omit<React.ComponentPropsWithoutRef<E>, 'as' | 'children'>;
function Box<E extends React.ElementType = 'div'>({
as,
children,
...props
}: PolymorphicProps<E>) {
const Component = as || 'div';
return <Component {...props}>{children}</Component>;
}
// Usage
<Box as="section" className="p-4">Content</Box>
<Box as="article" id="post-1">Article</Box>Component Size Guidelines
When to Split Components
Split a component when:
- It exceeds ~200 lines of code
- It has more than 7 props
- It renders conditionally complex UI sections
- Parts of it could be reused elsewhere
- It mixes multiple concerns (data + UI + layout)
Prop Drilling Solutions
// ❌ Prop drilling through multiple levels
<App theme={theme} user={user}>
<Layout theme={theme} user={user}>
<Sidebar theme={theme}>
<UserInfo user={user} />
</Sidebar>
</Layout>
</App>
// ✅ Context for cross-cutting values
const ThemeContext = createContext<Theme>(defaultTheme);
const UserContext = createContext<User | null>(null);
function App() {
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={user}>
<Layout>
<Sidebar>
<UserInfo />
</Sidebar>
</Layout>
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// ✅ Component composition (children pattern)
function Layout({ children }: { children: ReactNode }) {
return <div className="layout">{children}</div>;
}
function App() {
return (
<Layout>
<Sidebar>
<UserInfo user={user} /> {/* user passed directly */}
</Sidebar>
</Layout>
);
}State Management Decision Matrix
| State Type | Solution | When to Use |
|---|---|---|
| Local UI state | useState / useReducer | Toggle, form inputs, modals |
| Server state | TanStack Query / SWR | API data, caching, refetching |
| Form state | React Hook Form | Complex forms, validation |
| URL state | Router search params | Filters, pagination, sorting |
| Cross-component | Context (narrow scope) | Theme, auth, locale |
| Global app state | Zustand / Jotai | Shopping cart, notifications |
| Complex domain | Redux Toolkit | Rarely — only for complex interactions |
Error Handling Patterns
Error Boundaries
Every major section of the app should have an error boundary.
// Reusable error boundary component
'use client';
import { Component, type ErrorInfo, type ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback: ReactNode;
}
interface State {
hasError: boolean;
}
class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('Error boundary caught:', error, info);
}
render() {
if (this.state.hasError) return this.props.fallback;
return this.props.children;
}
}
// Usage
<ErrorBoundary fallback={<ErrorFallback />}>
<Dashboard />
</ErrorBoundary>Review Checklist
- [ ] Components follow single responsibility principle
- [ ] Component size is manageable (under ~200 lines)
- [ ] Props count is reasonable (under 7)
- [ ] State is colocated with the components that use it
- [ ] Context is scoped narrowly — not used as global state
- [ ] Server state uses TanStack Query / SWR, not useEffect + useState
- [ ] Error boundaries wrap major UI sections
- [ ] Components are properly typed with TypeScript
- [ ] Memoization (memo, useMemo, useCallback) is used only when measured benefit exists
React Hooks Best Practices and Common Mistakes
useState
Functional Updates for Derived State
Use functional updates when the new state depends on the previous state.
// ❌ May cause stale state in async contexts
setCount(count + 1);
// ✅ Always uses latest state
setCount(prev => prev + 1);Lazy Initialization
Use a function for expensive initial state computation.
// ❌ Runs on every render
const [data, setData] = useState(expensiveComputation(props));
// ✅ Runs only on first render
const [data, setData] = useState(() => expensiveComputation(props));useEffect
Always Include Cleanup Functions
When subscribing to external resources, always clean up.
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
const res = await fetch('/api/data', { signal: controller.signal });
const data = await res.json();
setData(data);
}
fetchData();
return () => controller.abort(); // Cleanup on unmount
}, []);Correct Dependency Arrays
Include all values from the component scope that change over time and are used in the effect.
// ❌ Missing dependency — stale closure
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1); // count is stale
}, 1000);
return () => clearInterval(interval);
}, []); // count missing from deps
// ✅ Functional update avoids stale closure
useEffect(() => {
const interval = setInterval(() => {
setCount(prev => prev + 1); // Always uses latest
}, 1000);
return () => clearInterval(interval);
}, []); // No external dependency neededAvoid Using useEffect for Data Fetching
Prefer TanStack Query, SWR, or Server Components for data fetching.
// ❌ Manual data fetching with useEffect
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
fetchUsers()
.then(data => { if (!cancelled) setUsers(data); })
.catch(err => { if (!cancelled) setError(err); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, []);
// ✅ TanStack Query handles loading, error, caching, refetching
const { data: users, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});useMemo and useCallback
When to Use useMemo
Use useMemo for expensive computations that depend on specific values.
// ✅ Appropriate: Expensive filtering/sorting
const filteredItems = useMemo(
() => items.filter(item => item.category === category).sort(sortByDate),
[items, category]
);
// ❌ Unnecessary: Simple calculations
const fullName = useMemo(() => `${first} ${last}`, [first, last]);
// Just do: const fullName = `${first} ${last}`;When to Use useCallback
Use useCallback when passing callbacks to memoized child components.
// ✅ Appropriate: Passed to React.memo component
const handleSelect = useCallback((id: string) => {
setSelected(id);
}, []);
<MemoizedList items={items} onSelect={handleSelect} />
// ❌ Unnecessary: Not passed to memoized child
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
<button onClick={handleClick}>Click</button>
// Just do: <button onClick={() => console.log('clicked')}>Click</button>useRef
Storing Mutable Values Without Re-renders
Use refs for values that don't trigger re-renders.
function Timer() {
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const [count, setCount] = useState(0);
function start() {
intervalRef.current = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
}
function stop() {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
}
return (
<div>
<p>{count}</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}Custom Hooks
Extracting Reusable Logic
// ✅ Reusable hook encapsulating complex logic
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;
}
// Usage
function SearchInput() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
const { data } = useQuery({
queryKey: ['search', debouncedQuery],
queryFn: () => searchAPI(debouncedQuery),
enabled: debouncedQuery.length > 2,
});
}Common Mistakes Summary
| Mistake | Impact | Fix |
|---|---|---|
| Missing useEffect cleanup | Memory leaks, stale updates | Always return cleanup function |
| Wrong dependency array | Stale closures, infinite loops | Include all used values |
| useEffect for data fetching | Race conditions, no caching | Use TanStack Query / SWR |
| useMemo everywhere | Code complexity, no benefit | Only for expensive computations |
| useCallback without memo | No performance benefit | Only with React.memo children |
| State for derived values | Unnecessary re-renders | Compute during render |
| Mutating state directly | Silent bugs, no re-render | Always create new references |