| name | react-patterns |
| description | Modern React development with hooks, component patterns, state management, and performance optimization for building scalable applications |
React Patterns - Modern Development Guide
A comprehensive skill for mastering modern React development patterns, including React 18+ features, hooks, component composition, state management strategies, and performance optimization techniques for building scalable web applications.
When to Use This Skill
Use this skill when:
- Building modern React applications with functional components and hooks
- Implementing complex state management with useReducer and Context API
- Optimizing React application performance with memoization techniques
- Creating reusable custom hooks for shared logic
- Working with Server Components and React Server Components (RSC)
- Managing forms, side effects, and asynchronous operations
- Refactoring class components to modern functional patterns
- Building type-safe React applications with proper patterns
- Implementing advanced component composition patterns
- Debugging React performance issues and unnecessary re-renders
Core Concepts
React Philosophy
Modern React emphasizes:
- Functional Components: Pure functions that return JSX
- Hooks: Composable state and side effect management
- Declarative UI: Describe what the UI should look like, React handles updates
- Component Composition: Build complex UIs from simple, reusable components
- Unidirectional Data Flow: Props flow down, events flow up
- Immutability: Never mutate state directly, always create new objects/arrays
Component Types
- Presentational Components: Focus on UI, receive data via props
- Container Components: Handle logic, state, and side effects
- Server Components: Render on the server, no client JavaScript
- Client Components: Interactive components marked with
"use client" - Async Components: Server components that can await data
React Hooks Reference
Built-in State Hooks
useState
Manage local component state for simple values.
Syntax:
const [state, setState] = useState(initialValue);
Basic Example:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(prev => prev + 1)}>Increment (functional)</button>
</div>
);
}
Best Practices:
- Use functional updates when new state depends on previous state
- Keep state as local as possible
- Don't store derived values in state
- Initialize with functions for expensive computations
Multiple State Variables:
function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);
return (
<form>
<input value={name} onChange={e => setName(e.target.value)} />
<input value={email} onChange={e => setEmail(e.target.value)} />
<input type="number" value={age} onChange={e => setAge(Number(e.target.value))} />
</form>
);
}
Lazy Initialization:
function ExpensiveComponent() {
// Function only runs once on initial render
const [state, setState] = useState(() => {
const initialValue = expensiveComputation();
return initialValue;
});
return <div>{state}</div>;
}
useReducer
Manage complex state logic with a reducer pattern (similar to Redux).
Syntax:
const [state, dispatch] = useReducer(reducer, initialState, init?);
Task Manager Example:
import { useReducer } from 'react';
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added':
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
case 'changed':
return tasks.map(t =>
t.id === action.task.id ? action.task : t
);
case 'deleted':
return tasks.filter(t => t.id !== action.id);
default:
throw Error('Unknown action: ' + action.type);
}
}
function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, []);
function handleAddTask(text) {
dispatch({
type: 'added',
id: Date.now(),
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId
});
}
return (
<>
<AddTask onAddTask={handleAddTask} />
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
When to use useReducer:
- Multiple related state values
- Complex state update logic
- State transitions follow predictable patterns
- Need to optimize performance with many updates
- Want to separate state logic from component
useContext
Access context values without prop drilling.
Syntax:
const value = useContext(SomeContext);
Theme Context Example:
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Usage
function Button() {
const { theme, toggleTheme } = useTheme();
return (
<button
onClick={toggleTheme}
className={theme === 'dark' ? 'btn-dark' : 'btn-light'}
>
Toggle Theme
</button>
);
}
Combining useReducer + useContext:
import { createContext, useContext, useReducer } from 'react';
const TasksContext = createContext(null);
const TasksDispatchContext = createContext(null);
export function TasksProvider({ children }) {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
return (
<TasksContext.Provider value={tasks}>
<TasksDispatchContext.Provider value={dispatch}>
{children}
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
export function useTasks() {
return useContext(TasksContext);
}
export function useTasksDispatch() {
return useContext(TasksDispatchContext);
}
// Usage in components
function TaskList() {
const tasks = useTasks();
const dispatch = useTasksDispatch();
return (
<ul>
{tasks.map(task => (
<li key={task.id}>
{task.text}
<button onClick={() => dispatch({ type: 'deleted', id: task.id })}>
Delete
</button>
</li>
))}
</ul>
);
}
Effect Hooks
useEffect
Synchronize component with external systems (APIs, DOM, subscriptions).
Syntax:
useEffect(() => {
// Effect logic
return () => {
// Cleanup logic
};
}, [dependencies]);
Data Fetching Example:
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false; // Prevent race conditions
async function fetchUser() {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!ignore) {
setUser(data);
}
} catch (err) {
if (!ignore) {
setError(err.message);
}
} finally {
if (!ignore) {
setLoading(false);
}
}
}
fetchUser();
return () => {
ignore = true; // Cleanup
};
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>User: {user?.name}</div>;
}
Subscription Example:
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const connection = createConnection(roomId);
connection.on('message', (msg) => {
setMessages(prev => [...prev, msg]);
});
connection.connect();
return () => {
connection.disconnect(); // Cleanup on unmount or roomId change
};
}, [roomId]);
return (
<div>
{messages.map((msg, i) => (
<div key={i}>{msg}</div>
))}
</div>
);
}
Common Effect Patterns:
// Run once on mount
useEffect(() => {
console.log('Component mounted');
}, []); // Empty dependency array
// Run on every render
useEffect(() => {
console.log('Component rendered');
}); // No dependency array
// Run when specific values change
useEffect(() => {
console.log('userId changed:', userId);
}, [userId]); // Dependency array with values
// Cleanup on unmount
useEffect(() => {
const timer = setTimeout(() => {
console.log('Delayed action');
}, 1000);
return () => clearTimeout(timer);
}, []);
useLayoutEffect
Synchronous version of useEffect, runs before browser paint.
Use Cases:
- Measuring DOM elements
- Synchronous DOM mutations
- Preventing visual flickering
Example:
import { useLayoutEffect, useRef, useState } from 'react';
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // Synchronous update before paint
}, []);
return (
<div ref={ref}>
Tooltip content (height: {tooltipHeight}px)
</div>
);
}
Performance Hooks
useMemo
Cache expensive computations between renders.
Syntax:
const cachedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Example:
import { useMemo } from 'react';
function TodoList({ todos, filter }) {
// Only recompute when todos or filter changes
const visibleTodos = useMemo(() => {
console.log('Filtering todos...');
return todos.filter(todo => {
if (filter === 'all') return true;
if (filter === 'active') return !todo.done;
if (filter === 'completed') return todo.done;
return true;
});
}, [todos, filter]);
return (
<ul>
{visibleTodos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
When to use useMemo:
- Expensive calculations (filtering, sorting large arrays)
- Preventing unnecessary re-renders of child components
- Stabilizing object/array references passed as dependencies
Anti-pattern (unnecessary):
// ❌ Don't use for simple calculations
const sum = useMemo(() => a + b, [a, b]); // Overkill
// ✅ Just compute directly
const sum = a + b;
useCallback
Memoize function references between renders.
Syntax:
const cachedFn = useCallback(() => {
// Function logic
}, [dependencies]);
Example:
import { useState, useCallback, memo } from 'react';
const TodoItem = memo(function TodoItem({ todo, onChange, onDelete }) {
console.log('TodoItem rendered:', todo.id);
return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onChange(todo)}
/>
{todo.text}
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
});
function TodoList() {
const [todos, setTodos] = useState([]);
// Memoize handlers to prevent TodoItem re-renders
const handleChange = useCallback((todo) => {
setTodos(prev => prev.map(t =>
t.id === todo.id ? { ...t, done: !t.done } : t
));
}, []);
const handleDelete = useCallback((id) => {
setTodos(prev => prev.filter(t => t.id !== id));
}, []);
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onChange={handleChange}
onDelete={handleDelete}
/>
))}
</ul>
);
}
When to use useCallback:
- Passing callbacks to memoized child components
- Function is a dependency of useEffect or other hooks
- Optimizing expensive event handlers
React.memo
Memoize entire component to prevent unnecessary re-renders.
Syntax:
const MemoizedComponent = memo(function Component(props) {
// Component logic
}, arePropsEqual?);
Example:
import { memo } from 'react';
const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
console.log('ExpensiveComponent rendered');
return (
<div>
{data.map(item => (
<div key={item.id} onClick={() => onClick(item)}>
{item.name}
</div>
))}
</div>
);
});
// With custom comparison
const CustomMemoComponent = memo(
function CustomMemoComponent({ user }) {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => {
// Return true if props are equal (skip re-render)
return prevProps.user.id === nextProps.user.id;
}
);
Ref Hooks
useRef
Create mutable reference that persists across renders.
Syntax:
const ref = useRef(initialValue);
DOM Reference Example:
import { useRef } from 'react';
function VideoPlayer({ src, isPlaying }) {
const videoRef = useRef(null);
useEffect(() => {
if (isPlaying) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
}, [isPlaying]);
return <video ref={videoRef} src={src} />;
}
Storing Mutable Values:
function Timer() {
const intervalRef = useRef(null);
const [count, setCount] = useState(0);
function start() {
if (intervalRef.current) return; // Already running
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
}
function stop() {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}
return (
<div>
<p>Count: {count}</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
Previous Value Pattern:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
// Usage
function Counter({ count }) {
const prevCount = usePrevious(count);
return (
<div>
<p>Current: {count}</p>
<p>Previous: {prevCount}</p>
</div>
);
}
Transition Hooks (React 18+)
useTransition
Mark state updates as non-urgent, keeping UI responsive.
Syntax:
const [isPending, startTransition] = useTransition();
Example:
import { useState, useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
function handleChange(e) {
const value = e.target.value;
setQuery(value); // Urgent update (input value)
startTransition(() => {
// Non-urgent update (search results)
const filtered = performExpensiveSearch(value);
setResults(filtered);
});
}
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <div>Searching...</div>}
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}
useDeferredValue
Defer updating part of the UI.
Syntax:
const deferredValue = useDeferredValue(value);
Example:
import { useState, useDeferredValue, memo } from 'react';
const SlowList = memo(function SlowList({ items }) {
// Intentionally slow rendering
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
});
function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList items={filterItems(deferredText)} />
</>
);
}
Server Action Hooks (RSC)
useActionState
Manage server action state and pending status.
Syntax:
const [state, formAction, isPending] = useActionState(serverAction, initialState);
Example:
"use client";
import { useActionState } from 'react';
import { updateName } from './actions';
function UpdateNameForm() {
const [state, submitAction, isPending] = useActionState(
updateName,
{ error: null }
);
return (
<form action={submitAction}>
<input type="text" name="name" disabled={isPending} />
{state.error && <span className="error">{state.error}</span>}
<button type="submit" disabled={isPending}>
{isPending ? 'Updating...' : 'Update'}
</button>
</form>
);
}
Server Action (actions.js):
"use server";
export async function updateName(prevState, formData) {
const name = formData.get('name');
if (!name) {
return { error: 'Name is required' };
}
try {
await db.users.updateName(name);
return { error: null };
} catch (err) {
return { error: 'Failed to update name' };
}
}
Custom Hooks Patterns
Custom hooks let you extract and reuse stateful logic across components.
Naming Convention
Always prefix custom hooks with use:
useCustomHook // ✅ Correct
customHook // ❌ Wrong
Data Fetching Hook
import { useState, useEffect } from 'react';
export function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false;
async function fetchData() {
try {
setLoading(true);
setError(null);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (!ignore) {
setData(result);
}
} catch (err) {
if (!ignore) {
setError(err.message);
}
} finally {
if (!ignore) {
setLoading(false);
}
}
}
fetchData();
return () => {
ignore = true;
};
}, [url]);
return { data, loading, error };
}
// Usage
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user.name}</div>;
}
Form Input Hook
import { useState } from 'react';
export function useFormInput(initialValue = '') {
const [value, setValue] = useState(initialValue);
function handleChange(e) {
setValue(e.target.value);
}
function reset() {
setValue(initialValue);
}
return {
value,
onChange: handleChange,
reset
};
}
// Usage
function LoginForm() {
const email = useFormInput('');
const password = useFormInput('');
function handleSubmit(e) {
e.preventDefault();
console.log('Email:', email.value);
console.log('Password:', password.value);
email.reset();
password.reset();
}
return (
<form onSubmit={handleSubmit}>
<input type="email" {...email} placeholder="Email" />
<input type="password" {...password} placeholder="Password" />
<button type="submit">Login</button>
</form>
);
}
Local Storage Hook
import { useState, useEffect } from 'react';
export function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
}, [key, value]);
return [value, setValue];
}
// Usage
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const [language, setLanguage] = useLocalStorage('language', 'en');
return (
<div>
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<select value={language} onChange={e => setLanguage(e.target.value)}>
<option value="en">English</option>
<option value="es">Spanish</option>
</select>
</div>
);
}
Debounce Hook
import { useState, useEffect } from 'react';
export function useDebounce(value, delay = 500) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// Usage
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
// Perform search
console.log('Searching for:', debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return (
<input
type="text"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
}
Window Size Hook
import { useState, useEffect } from 'react';
export function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
handleResize(); // Set initial size
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}
// Usage
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return (
<div>
<p>Window width: {width}px</p>
<p>Window height: {height}px</p>
{width < 768 ? <MobileView /> : <DesktopView />}
</div>
);
}
Online Status Hook
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
// Usage
function StatusBar() {
const isOnline = useOnlineStatus();
return (
<div className={isOnline ? 'online' : 'offline'}>
{isOnline ? '✅ Online' : '❌ Offline'}
</div>
);
}
Component Patterns
Compound Components
Build components that work together while sharing implicit state.
import { createContext, useContext, useState } from 'react';
const TabsContext = createContext();
export function Tabs({ children, defaultValue }) {
const [activeTab, setActiveTab] = useState(defaultValue);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
export function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}
export function Tab({ value, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
const isActive = activeTab === value;
return (
<button
className={`tab ${isActive ? 'active' : ''}`}
onClick={() => setActiveTab(value)}
>
{children}
</button>
);
}
export function TabPanels({ children }) {
return <div className="tab-panels">{children}</div>;
}
export function TabPanel({ value, children }) {
const { activeTab } = useContext(TabsContext);
if (activeTab !== value) return null;
return <div className="tab-panel">{children}</div>;
}
// Usage
function App() {
return (
<Tabs defaultValue="tab1">
<TabList>
<Tab value="tab1">Tab 1</Tab>
<Tab value="tab2">Tab 2</Tab>
<Tab value="tab3">Tab 3</Tab>
</TabList>
<TabPanels>
<TabPanel value="tab1">Content for Tab 1</TabPanel>
<TabPanel value="tab2">Content for Tab 2</TabPanel>
<TabPanel value="tab3">Content for Tab 3</TabPanel>
</TabPanels>
</Tabs>
);
}
Render Props
Share code between components using a prop whose value is a function.
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return render({ data, loading });
}
// Usage
function App() {
return (
<DataFetcher
url="/api/users"
render={({ data, loading }) => (
loading ? <div>Loading...</div> : (
<ul>
{data?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
)}
/>
);
}
Higher-Order Components (HOC)
Wrap components to enhance functionality.
import { useEffect, useState } from 'react';
// HOC for data fetching
function withData(Component, url) {
return function WithDataComponent(props) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, []);
return <Component {...props} data={data} loading={loading} />;
};
}
// Original component
function UserList({ data, loading }) {
if (loading) return <div>Loading...</div>;
return (
<ul>
{data?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// Enhanced component
const UserListWithData = withData(UserList, '/api/users');
// Usage
function App() {
return <UserListWithData />;
}
Container/Presenter Pattern
Separate logic from presentation.
// Container (logic)
function UserListContainer() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
fetchUsers().then(data => {
setUsers(data);
setLoading(false);
});
}, []);
const filteredUsers = users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<UserListPresenter
users={filteredUsers}
loading={loading}
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
/>
);
}
// Presenter (UI)
function UserListPresenter({ users, loading, searchTerm, onSearchChange }) {
if (loading) return <div>Loading...</div>;
return (
<div>
<input
type="text"
value={searchTerm}
onChange={e => onSearchChange(e.target.value)}
placeholder="Search users..."
/>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
Performance Optimization
Identifying Performance Issues
Use React DevTools Profiler to identify:
- Components re-rendering unnecessarily
- Expensive render operations
- State update cascades
Optimization Strategies
1. Memoization
import { memo, useMemo, useCallback } from 'react';
const ExpensiveList = memo(function ExpensiveList({ items, onItemClick }) {
console.log('Rendering ExpensiveList');
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => onItemClick(item.id)}>
{item.name}
</li>
))}
</ul>
);
});
function App() {
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);
// Memoize expensive computation
const sortedItems = useMemo(() => {
console.log('Sorting items...');
return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
// Memoize callback
const handleItemClick = useCallback((id) => {
console.log('Item clicked:', id);
}, []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<ExpensiveList items={sortedItems} onItemClick={handleItemClick} />
</div>
);
}
2. Code Splitting
import { lazy, Suspense } from 'react';
// Lazy load components
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const Dashboard = lazy(() => import('./Dashboard'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
// Route-based splitting
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
3. Virtualization
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={500}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}
4. Avoid Anonymous Functions
// ❌ Bad - creates new function on every render
function List({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => console.log(item.id)}>
{item.name}
</li>
))}
</ul>
);
}
// ✅ Good - use useCallback
function List({ items }) {
const handleClick = useCallback((id) => {
console.log(id);
}, []);
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</li>
))}
</ul>
);
}
Server Components (RSC)
React Server Components render on the server, reducing client bundle size.
Server Component
// app/users/page.js (Server Component by default)
async function UsersPage() {
// Fetch data directly on server
const users = await db.users.findAll();
return (
<div>
<h1>Users</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default UsersPage;
Client Component
// components/Counter.js
"use client"; // Mark as client component
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}
Composition Pattern
// app/page.js (Server Component)
import Counter from '@/components/Counter'; // Client Component
async function HomePage() {
const data = await fetchData(); // Server-side data fetching
return (
<div>
<h1>{data.title}</h1>
<p>{data.description}</p>
<Counter /> {/* Interactive client component */}
</div>
);
}
export default HomePage;
State Management Strategies
Local State (useState)
Best for: Component-specific state
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
Lifted State
Best for: Shared state between sibling components
function Parent() {
const [sharedValue, setSharedValue] = useState('');
return (
<>
<ChildA value={sharedValue} onChange={setSharedValue} />
<ChildB value={sharedValue} />
</>
);
}
Context API
Best for: App-wide state (theme, auth, language)
const AuthContext = createContext();
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = async (credentials) => {
const user = await api.login(credentials);
setUser(user);
};
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
useReducer + Context
Best for: Complex state logic with multiple actions
const StateContext = createContext();
const DispatchContext = createContext();
function reducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
return { ...state, items: [...state.items, action.payload] };
case 'REMOVE_ITEM':
return { ...state, items: state.items.filter(i => i.id !== action.payload) };
default:
return state;
}
}
function StoreProvider({ children }) {
const [state, dispatch] = useReducer(reducer, { items: [] });
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
Best Practices
1. Component Design
- Keep components small and focused (Single Responsibility)
- Extract reusable logic into custom hooks
- Prefer composition over inheritance
- Use prop spreading sparingly
- Define PropTypes or TypeScript types
2. State Management
- Keep state as local as possible
- Lift state only when necessary
- Avoid unnecessary state (derive from props when possible)
- Use reducers for complex state logic
- Batch state updates when possible
3. Performance
- Don't optimize prematurely - measure first
- Use React DevTools Profiler
- Memoize expensive computations
- Avoid creating objects/arrays in render
- Use key prop correctly for lists
4. Effects
- Keep effects focused (one concern per effect)
- Always specify dependencies
- Clean up effects (return cleanup function)
- Use AbortController for fetch requests
- Avoid race conditions with ignore flags
5. Code Organization
src/
├── components/
│ ├── common/ # Reusable UI components
│ ├── features/ # Feature-specific components
│ └── layout/ # Layout components
├── hooks/ # Custom hooks
├── context/ # Context providers
├── utils/ # Utility functions
├── services/ # API services
└── types/ # TypeScript types
Common Anti-Patterns to Avoid
1. Mutating State
// ❌ Wrong
const handleAdd = () => {
items.push(newItem);
setItems(items);
};
// ✅ Correct
const handleAdd = () => {
setItems([...items, newItem]);
};
2. Missing Dependencies
// ❌ Wrong
useEffect(() => {
console.log(userId);
}, []); // Missing userId dependency
// ✅ Correct
useEffect(() => {
console.log(userId);
}, [userId]);
3. Conditional Hooks
// ❌ Wrong
if (condition) {
const [state, setState] = useState(0);
}
// ✅ Correct
const [state, setState] = useState(0);
if (condition) {
// Use state
}
4. Creating Components Inside Components
// ❌ Wrong
function Parent() {
function Child() { // Re-created on every render
return <div>Child</div>;
}
return <Child />;
}
// ✅ Correct
function Child() {
return <div>Child</div>;
}
function Parent() {
return <Child />;
}
Troubleshooting
Common Issues
Issue: Infinite re-render loop
- Check useEffect dependencies
- Avoid setting state directly in render
- Use functional updates:
setState(prev => prev + 1)
Issue: Stale closure
- Use functional updates in setState
- Add missing dependencies to useEffect
- Use useRef for mutable values
Issue: Component not re-rendering
- Check if state is being mutated (use immutable updates)
- Verify React.memo comparison function
- Ensure new reference for objects/arrays
Issue: Memory leak warning
- Clean up effects (return cleanup function)
- Cancel ongoing requests on unmount
- Clear timers/intervals
Resources
- React Documentation: https://react.dev
- React Hooks: https://react.dev/reference/react
- React Server Components: https://react.dev/reference/rsc
- React DevTools: https://react.dev/learn/react-developer-tools
- Patterns: https://patterns.dev/react
Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Frontend Development, React, JavaScript Compatible With: React 18+, Next.js 13+, TypeScript