Modern React Patterns for 2024
Modern React Patterns for 2024
React has evolved significantly over the years, and with it, the patterns and practices that help us build maintainable, scalable applications. Let's explore some of the most important patterns every React developer should know in 2024.
1. Composition Over Inheritance
React's component model shines when we embrace composition. Instead of creating complex inheritance hierarchies, we can build flexible components through composition.
// ❌ Avoid inheritance-like patterns
const RedButton = ({ children, ...props }) => (
<Button color="red" {...props}>{children}</Button>
)
// ✅ Embrace composition
const Button = ({ variant, children, ...props }) => {
const variants = {
primary: 'bg-blue-500 text-white',
danger: 'bg-red-500 text-white',
ghost: 'bg-transparent border'
}
return (
<button
className={variants[variant]}
{...props}
>
{children}
</button>
)
}2. Custom Hooks for Logic Reuse
Custom hooks are the secret weapon for sharing stateful logic between components without the complexity of higher-order components.
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
return initialValue
}
})
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
window.localStorage.setItem(key, JSON.stringify(valueToStore))
} catch (error) {
console.log(error)
}
}
return [storedValue, setValue] as const
}3. Error Boundaries for Graceful Failures
With React's error boundaries, we can catch JavaScript errors anywhere in the component tree and display fallback UI.
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.log('Error caught by boundary:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return this.props.fallback || <h1>Something went wrong.</h1>
}
return this.props.children
}
}4. Render Props Pattern
The render props pattern is powerful for sharing code between components using a prop whose value is a function.
interface MouseProps {
children: (mouse: { x: number; y: number }) => React.ReactNode
}
function Mouse({ children }: MouseProps) {
const [mouse, setMouse] = useState({ x: 0, y: 0 })
const handleMouseMove = (event: MouseEvent) => {
setMouse({ x: event.clientX, y: event.clientY })
}
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove)
return () => window.removeEventListener('mousemove', handleMouseMove)
}, [])
return children(mouse)
}
// Usage
<Mouse>
{({ x, y }) => (
<div>Mouse position: {x}, {y}</div>
)}
</Mouse>5. Context for State Management
React Context provides a way to pass data through the component tree without having to pass props down manually at every level.
interface ThemeContextType {
theme: 'light' | 'dark'
toggleTheme: () => void
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('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 === undefined) {
throw new Error('useTheme must be used within a ThemeProvider')
}
return context
}Key Takeaways
- Embrace composition over complex inheritance patterns
- Extract logic into custom hooks for better reusability
- Use error boundaries to handle failures gracefully
- Leverage render props for flexible component APIs
- Use Context wisely for state that truly needs to be global
These patterns, when applied thoughtfully, will help you build React applications that are not only functional but also maintainable and scalable.
What's Next?
In upcoming posts, we'll dive deeper into performance optimization techniques, advanced TypeScript patterns with React, and how to integrate AI capabilities into your React applications.
What patterns do you find most useful in your React development? Let me know in the comments!