- Published on
Advanced React Patterns and Performance Optimization
Advanced React Patterns and Performance Optimization
In the ever-evolving landscape of React development, mastering advanced patterns and performance optimization techniques is crucial for building scalable, maintainable applications. This comprehensive guide explores cutting-edge React patterns and optimization strategies that will elevate your development skills to the next level. 🚀
Table of Contents
- Higher-Order Components (HOCs)
- Render Props Pattern
- Compound Components
- Custom Hooks Patterns
- Performance Optimization Techniques
- Advanced State Management
- Best Practices and Anti-patterns
Higher-Order Components (HOCs)
Higher-Order Components are a powerful pattern for reusing component logic. They're functions that take a component and return a new component with additional functionality.
Basic HOC Implementation
// withLoading HOC
const withLoading = (WrappedComponent) => {
return function WithLoadingComponent({ isLoading, ...props }) {
if (isLoading) {
return <div className="loading-spinner">Loading...</div>
}
return <WrappedComponent {...props} />
}
}
// Usage
const UserProfile = ({ user }) => (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
const UserProfileWithLoading = withLoading(UserProfile)
// In your component
function App() {
const [user, setUser] = useState(null)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
fetchUser().then((userData) => {
setUser(userData)
setIsLoading(false)
})
}, [])
return <UserProfileWithLoading isLoading={isLoading} user={user} />
}
Advanced HOC with Error Boundaries
const withErrorBoundary = (WrappedComponent) => {
return class extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error) {
return { hasError: true, error }
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by HOC:', error, errorInfo)
// Log to error reporting service
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Something went wrong</h2>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try again
</button>
</div>
)
}
return <WrappedComponent {...this.props} />
}
}
}
Render Props Pattern
The render props pattern allows components to share code using a prop whose value is a function.
// Data fetcher component using render props
class DataFetcher extends React.Component {
constructor(props) {
super(props)
this.state = { data: null, loading: true, error: null }
}
async componentDidMount() {
try {
const response = await fetch(this.props.url)
const data = await response.json()
this.setState({ data, loading: false })
} catch (error) {
this.setState({ error, loading: false })
}
}
render() {
return this.props.render(this.state)
}
}
// Usage
function UserList() {
return (
<DataFetcher
url="/api/users"
render={({ data, loading, error }) => {
if (loading) return <div>Loading users...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}}
/>
)
}
Modern Render Props with Hooks
// Custom hook replacing render props
function useDataFetcher(url) {
const [state, setState] = useState({ data: null, loading: true, error: null })
useEffect(() => {
let isMounted = true
const fetchData = async () => {
try {
const response = await fetch(url)
const data = await response.json()
if (isMounted) {
setState({ data, loading: false, error: null })
}
} catch (error) {
if (isMounted) {
setState({ data: null, loading: false, error })
}
}
}
fetchData()
return () => {
isMounted = false
}
}, [url])
return state
}
// Usage
function UserList() {
const { data, loading, error } = useDataFetcher('/api/users')
if (loading) return <div>Loading users...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
Compound Components
Compound components work together to form a complete UI component, providing flexibility and reusability.
// Tabs compound component
const TabsContext = React.createContext()
function Tabs({ children, defaultActiveKey }) {
const [activeKey, setActiveKey] = useState(defaultActiveKey)
return (
<TabsContext.Provider value={{ activeKey, setActiveKey }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
)
}
function TabList({ children }) {
return <div className="tab-list">{children}</div>
}
function Tab({ eventKey, children }) {
const { activeKey, setActiveKey } = useContext(TabsContext)
const isActive = activeKey === eventKey
return (
<button className={`tab ${isActive ? 'active' : ''}`} onClick={() => setActiveKey(eventKey)}>
{children}
</button>
)
}
function TabPanels({ children }) {
return <div className="tab-panels">{children}</div>
}
function TabPanel({ eventKey, children }) {
const { activeKey } = useContext(TabsContext)
if (activeKey !== eventKey) return null
return <div className="tab-panel">{children}</div>
}
// Compound component assignment
Tabs.List = TabList
Tabs.Tab = Tab
Tabs.Panels = TabPanels
Tabs.Panel = TabPanel
// Usage
function App() {
return (
<Tabs defaultActiveKey="tab1">
<Tabs.List>
<Tabs.Tab eventKey="tab1">Tab 1</Tabs.Tab>
<Tabs.Tab eventKey="tab2">Tab 2</Tabs.Tab>
<Tabs.Tab eventKey="tab3">Tab 3</Tabs.Tab>
</Tabs.List>
<Tabs.Panels>
<Tabs.Panel eventKey="tab1">Content for Tab 1</Tabs.Panel>
<Tabs.Panel eventKey="tab2">Content for Tab 2</Tabs.Panel>
<Tabs.Panel eventKey="tab3">Content for Tab 3</Tabs.Panel>
</Tabs.Panels>
</Tabs>
)
}
Custom Hooks Patterns
Advanced State Management Hook
// useReducerWithMiddleware hook
function useReducerWithMiddleware(reducer, initialState, middleware = []) {
const [state, dispatch] = useReducer(reducer, initialState)
const enhancedDispatch = useCallback(
(action) => {
// Apply middleware
const chain = middleware.map((mw) => mw(state))
const composedDispatch = chain.reduce((acc, mw) => mw(acc), dispatch)
return composedDispatch(action)
},
[middleware, state]
)
return [state, enhancedDispatch]
}
// Logging middleware
const loggingMiddleware = (state) => (next) => (action) => {
console.log('Previous state:', state)
console.log('Action:', action)
const result = next(action)
console.log('Next state:', state)
return result
}
// Usage
function Counter() {
const [state, dispatch] = useReducerWithMiddleware(counterReducer, { count: 0 }, [
loggingMiddleware,
])
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
</div>
)
}
Async State Management Hook
// useAsyncState hook
function useAsyncState(asyncFunction, dependencies = []) {
const [state, setState] = useState({
data: null,
loading: false,
error: null,
})
const execute = useCallback(async (...args) => {
setState({ data: null, loading: true, error: null })
try {
const result = await asyncFunction(...args)
setState({ data: result, loading: false, error: null })
return result
} catch (error) {
setState({ data: null, loading: false, error })
throw error
}
}, dependencies)
return { ...state, execute }
}
// Usage
function UserProfile({ userId }) {
const {
data: user,
loading,
error,
execute,
} = useAsyncState(async (id) => {
const response = await fetch(`/api/users/${id}`)
return response.json()
}, [])
useEffect(() => {
if (userId) {
execute(userId)
}
}, [userId, execute])
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
if (!user) return <div>No user found</div>
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
Performance Optimization Techniques
React.memo and useMemo
// Expensive component that should be memoized
const ExpensiveComponent = React.memo(
({ data, onUpdate }) => {
const processedData = useMemo(() => {
// Expensive computation
return data.map((item) => ({
...item,
processed: heavyComputation(item),
}))
}, [data])
return (
<div>
{processedData.map((item) => (
<div key={item.id} onClick={() => onUpdate(item.id)}>
{item.name}: {item.processed}
</div>
))}
</div>
)
},
(prevProps, nextProps) => {
// Custom comparison function
return (
prevProps.data.length === nextProps.data.length &&
prevProps.data.every((item, index) => item.id === nextProps.data[index].id)
)
}
)
useCallback for Event Handlers
function TodoList({ todos, onToggle, onDelete }) {
// Memoize callbacks to prevent unnecessary re-renders
const handleToggle = useCallback(
(id) => {
onToggle(id)
},
[onToggle]
)
const handleDelete = useCallback(
(id) => {
onDelete(id)
},
[onDelete]
)
return (
<div>
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} onDelete={handleDelete} />
))}
</div>
)
}
const TodoItem = React.memo(({ todo, onToggle, onDelete }) => {
return (
<div>
<span onClick={() => onToggle(todo.id)}>
{todo.completed ? '✓' : '○'} {todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</div>
)
})
Virtual Scrolling
// Simple virtual scrolling implementation
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0)
const visibleStart = Math.floor(scrollTop / itemHeight)
const visibleEnd = Math.min(
visibleStart + Math.ceil(containerHeight / itemHeight),
items.length - 1
)
const visibleItems = items.slice(visibleStart, visibleEnd + 1)
const offsetY = visibleStart * itemHeight
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<div key={visibleStart + index} style={{ height: itemHeight }}>
{item.content}
</div>
))}
</div>
</div>
</div>
)
}
Advanced State Management
Context with Reducer Pattern
// Advanced context pattern with reducer
const AppStateContext = createContext()
const AppDispatchContext = createContext()
function appReducer(state, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload }
case 'SET_THEME':
return { ...state, theme: action.payload }
case 'ADD_NOTIFICATION':
return {
...state,
notifications: [...state.notifications, action.payload],
}
case 'REMOVE_NOTIFICATION':
return {
...state,
notifications: state.notifications.filter((n) => n.id !== action.payload),
}
default:
throw new Error(`Unhandled action type: ${action.type}`)
}
}
function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, {
user: null,
theme: 'light',
notifications: [],
})
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>{children}</AppDispatchContext.Provider>
</AppStateContext.Provider>
)
}
// Custom hooks for consuming context
function useAppState() {
const context = useContext(AppStateContext)
if (!context) {
throw new Error('useAppState must be used within AppProvider')
}
return context
}
function useAppDispatch() {
const context = useContext(AppDispatchContext)
if (!context) {
throw new Error('useAppDispatch must be used within AppProvider')
}
return context
}
Best Practices and Anti-patterns
✅ Best Practices
- Use TypeScript for better type safety
- Implement proper error boundaries
- Optimize bundle size with code splitting
- Use React DevTools Profiler for performance analysis
- Implement proper accessibility (a11y)
❌ Anti-patterns to Avoid
- Mutating state directly
- Using array indices as keys
- Not cleaning up effects
- Over-using useEffect
- Prop drilling without context
Code Splitting Example
// Lazy loading with Suspense
const LazyComponent = React.lazy(() => import('./LazyComponent'))
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
)
}
Conclusion
Mastering these advanced React patterns and optimization techniques will significantly improve your application's performance, maintainability, and user experience. Remember to:
- Choose the right pattern for your specific use case
- Profile your application to identify performance bottlenecks
- Keep components small and focused
- Use TypeScript for better development experience
- Always consider accessibility in your implementations
By implementing these patterns thoughtfully, you'll build React applications that are not only performant but also maintainable and scalable. Happy coding! 🚀