React Best Practices: Components, Hooks, and Performance
Comprehensive guide to React development — component patterns, hooks, performance optimization, and code organization
Introduction
React is a JavaScript library for building user interfaces. Writing clean, performant React code requires understanding component architecture, hooks, state management, and performance patterns. This guide covers the essential practices for professional React development.
Component Architecture
Functional Components
Always prefer functional components over class components:
// ❌ Class component (avoid)
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// ✅ Functional component (preferred)
function Welcome({ name }) {
return <h1>Hello, {name}</h1>;
}
Component Composition
Compose small, focused components rather than building monoliths:
// ❌ Monolithic component
function Dashboard() {
return (
<div>
<header>{/* 50 lines of header */}</header>
<aside>{/* 30 lines of sidebar */}</aside>
<main>{/* 100 lines of content */}</main>
<footer>{/* 20 lines of footer */}</footer>
</div>
);
}
// ✅ Composed from smaller components
function Dashboard() {
return (
<div>
<DashboardHeader />
<DashboardSidebar />
<DashboardContent />
<DashboardFooter />
</div>
);
}
Props Typing
Always type your props with TypeScript or PropTypes:
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
children?: React.ReactNode;
}
function Button({ label, onClick, variant = 'primary', disabled, children }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled} className={`btn btn-${variant}`}>
{children || label}
</button>
);
}
Hooks
useState
// Basic state
const [count, setCount] = useState(0);
// Functional update (when new state depends on previous)
setCount(prev => prev + 1);
// Lazy initialization (expensive computation)
const [data, setData] = useState(() => computeExpensiveInitialValue());
// Object state
const [user, setUser] = useState({ name: '', email: '' });
setUser(prev => ({ ...prev, name: 'Alice' }));
useEffect
Use sparingly — most logic belongs in event handlers:
// ✅ Sync with external system (browser API)
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
// ✅ Event subscription with cleanup
useEffect(() => {
const handler = (e) => handleKeyPress(e);
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, []);
// ❌ Don't use useEffect for derived state
// Derive values during render instead
const fullName = `${firstName} ${lastName}`; // Not in useEffect!
useMemo and useCallback
Use for expensive computations and stable references:
// ✅ useMemo: cache expensive computation
const sortedList = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
// ✅ useCallback: stable function reference
const handleClick = useCallback((id) => {
setSelected(prev => prev === id ? null : id);
}, []);
// ❌ Don't memoize everything — only when needed
// Simple computations don't need useMemo
Custom Hooks
Extract reusable logic into custom hooks:
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Usage
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>{theme}</button>;
}
Performance
Avoid Unnecessary Re-renders
// ✅ React.memo for pure components
const UserCard = React.memo(function UserCard({ user }) {
return <div>{user.name}</div>;
});
// ✅ Move state down to isolate re-renders
function Parent() {
return (
<div>
<ExpensiveChild />
<Counter /> {/* State changes only affect Counter */}
</div>
);
}
// ✅ Use key to reset component state
function UserProfile({ userId }) {
return <Profile key={userId} userId={userId} />;
}
Lazy Loading
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Skeleton />}>
<HeavyComponent />
</Suspense>
);
}
State Management
Lifting State Up
// ✅ Shared state goes to closest common ancestor
function Parent() {
const [selected, setSelected] = useState(null);
return (
<>
<List items={items} selected={selected} onSelect={setSelected} />
<Detail item={selected} />
</>
);
}
Context for Global State
const ThemeContext = createContext('light');
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Content />
</ThemeContext.Provider>
);
}
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return <button className={theme}>Toggle</button>;
}
Code Organization
File Structure
src/
components/
Button/
Button.tsx
Button.test.tsx
Button.module.css
UserCard/
UserCard.tsx
UserCard.test.tsx
hooks/
useLocalStorage.ts
useDebounce.ts
utils/
formatDate.ts
api.ts
pages/
Home.tsx
About.tsx
Naming Conventions
- Components: PascalCase files (
UserCard.tsx) - Hooks: camelCase with
useprefix (useLocalStorage.ts) - Utilities: camelCase (
formatDate.ts) - One component per file (with co-located tests)
Best Practices
Do's
// ✅ Keep components small and focused
// ✅ Use TypeScript for props
// ✅ Derive state during render, not in effects
// ✅ Handle loading, error, and empty states
// ✅ Use semantic HTML and ARIA attributes
// ✅ Co-locate tests with components
Don'ts
// ❌ Don't mutate state directly
state.value = newValue; // Wrong!
// ❌ Don't use useEffect for derived state
useEffect(() => setFullName(first + ' ' + last), [first, last]);
// ❌ Don't create components inside components
function Parent() {
function Child() { return <div />; } // New reference every render!
}
// ❌ Don't ignore keys in lists
items.map(item => <div>{item.name}</div>); // Missing key!
// ❌ Don't over-memoize
const doubled = useMemo(() => count * 2, [count]); // Unnecessary
Summary Table
| Concept | Pattern | When to Use |
|---|---|---|
| Components | Functional + TypeScript | Always |
| State | useState | Component-local state |
| Side effects | useEffect | Browser APIs, subscriptions |
| Memoization | useMemo/useCallback | Expensive computations |
| Reusability | Custom hooks | Shared logic |
| Performance | React.memo | Pure components |
| Code splitting | lazy + Suspense | Large components |
| Global state | Context or Zustand | Shared app state |
Conclusion
Professional React development is about:
- Small, focused components that compose well
- Hooks for state and side effects (used sparingly)
- TypeScript for type safety and documentation
- Performance awareness without premature optimization
- Clean code organization with consistent naming
Master these patterns and you'll build React applications that are maintainable, performant, and a joy to work with.
#react #typescript #javascript #hooks #performance #web-development #frontend