Modern React Patterns
Explore the latest React patterns and hooks for better component design
Introduction
React has evolved significantly over the years, introducing powerful patterns and hooks that enable developers to write more efficient, maintainable, and reusable components. Let's explore some of the most important modern React patterns.
Custom Hooks
Custom hooks allow you to extract component logic into reusable functions, promoting code reuse and separation of concerns.
Example: useLocalStorage Hook
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
} Compound Components Pattern
This pattern allows you to create components that work together to form a cohesive UI, providing flexibility while maintaining a clean API.
Example: Modal Component
const Modal = ({ children, isOpen, onClose }) => {
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>
);
};
Modal.Header = ({ children }) => (
<div className="modal-header">{children}</div>
);
Modal.Body = ({ children }) => (
<div className="modal-body">{children}</div>
);
Modal.Footer = ({ children }) => (
<div className="modal-footer">{children}</div>
); Render Props Pattern
Render props is a technique for sharing code between components using a prop whose value is a function.
Example: Data Fetcher
const DataFetcher = ({ url, render }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
});
}, [url]);
return render({ data, loading, error });
}; Higher-Order Components (HOCs)
HOCs are functions that take a component and return a new component with additional props or behavior.
Example: withAuth HOC
const withAuth = (WrappedComponent) => {
return function AuthenticatedComponent(props) {
const { user, loading } = useAuth();
if (loading) {
return <div>Loading...</div>;
}
if (!user) {
return <div>Please log in to access this page.</div>;
}
return <WrappedComponent {...props} user={user} />;
};
}; Context and useReducer Pattern
Combining Context API with useReducer provides a powerful state management solution for complex application state.
Example: Theme Context
const ThemeContext = createContext();
const themeReducer = (state, action) => {
switch (action.type) {
case 'TOGGLE_THEME':
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
case 'SET_THEME':
return { ...state, theme: action.payload };
default:
return state;
}
};
export const ThemeProvider = ({ children }) => {
const [state, dispatch] = useReducer(themeReducer, { theme: 'light' });
return (
<ThemeContext.Provider value={{ state, dispatch }}>
{children}
</ThemeContext.Provider>
);
}; Error Boundaries
Error boundaries catch JavaScript errors anywhere in the component tree and display a fallback UI instead of crashing the entire application.
Example: Error Boundary
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
} Performance Optimization Patterns
1. React.memo
Prevents unnecessary re-renders of functional components:
const ExpensiveComponent = React.memo(({ data }) => {
return <div>{/* Expensive rendering logic */}</div>;
}); 2. useMemo and useCallback
Optimize expensive calculations and prevent unnecessary function recreations:
const MyComponent = ({ items, onItemClick }) => {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
const handleClick = useCallback((id) => {
onItemClick(id);
}, [onItemClick]);
return <div>{/* Component JSX */}</div>;
}; Conclusion
These modern React patterns provide powerful tools for building scalable, maintainable applications. Choose the right pattern based on your specific use case, and remember that simpler solutions are often better than complex ones.