React's flexibility allows for many different patterns and approaches. In this article, we dive deep into advanced patterns that help create scalable, maintainable applications.
Compound Components Pattern
Compound components allow you to create flexible and reusable component APIs. This pattern is used extensively in libraries like React Router and Reach UI.
Example Implementation:
const Tabs = ({ children, defaultTab = 0 }) => {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<div className="tabs">
{React.Children.map(children, (child, index) =>
React.cloneElement(child, { activeTab, setActiveTab, index })
)}
</div>
);
};
const TabList = ({ children, activeTab, setActiveTab }) => (
<div className="tab-list">
{React.Children.map(children, (child, index) =>
React.cloneElement(child, {
isActive: activeTab === index,
onClick: () => setActiveTab(index)
})
)}
</div>
);
Render Props Pattern
Render props provide a way to share code between components using a prop whose value is a function.
Benefits:
- Highly flexible and reusable
- Separation of concerns
- Testable logic isolation
Example:
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 });
};
Custom Hooks for Logic Reuse
Custom hooks extract component logic into reusable functions, following the same rules as built-in hooks.
Example: useLocalStorage Hook
const 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 saving to localStorage:', error);
}
};
return [storedValue, setValue];
};
Higher-Order Components (HOCs)
While less common in modern React, HOCs still have their place for cross-cutting concerns like authentication or analytics.
Performance Optimization Patterns
React.memo and useMemo
- Use React.memo for expensive component renders
- useMemo for expensive calculations
- useCallback for stable function references
Code Splitting
- React.lazy for component-level splitting
- Dynamic imports for route-based splitting
Conclusion
These patterns provide powerful tools for building scalable React applications. Choose the right pattern based on your specific use case and team preferences. Remember that simpler solutions are often better than complex abstractions.