Higher-Order Components (HOCs)
Learn Higher-Order Components for cross-cutting concerns and component composition. This is a foundational concept in component-based UI development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React experience. Take your time with each section and practice the examples
What are HOCs?
Higher-Order Components (HOCs) are functions that take a component and return a new component with additional props or behavior. They are useful for sharing logic between components.. This is an essential concept that every React developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Basic HOC Pattern
// Basic HOC example
import React from 'react';
// HOC for adding loading state
function withLoading(WrappedComponent) {
return function WithLoadingComponent(props) {
const [loading, setLoading] = React.useState(false);
const [data, setData] = React.useState(null);
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(props.url);
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
};
React.useEffect(() => {
fetchData();
}, [props.url]);
if (loading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} data={data} />;
};
}
// Component to be enhanced
function UserList({ data }) {
if (!data) return <div>No data available</div>;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// Enhanced component
const UserListWithLoading = withLoading(UserList);
// Usage
function App() {
return <UserListWithLoading url="/api/users" />;
}
// HOC for authentication
function withAuth(WrappedComponent) {
return function WithAuthComponent(props) {
const [user, setUser] = React.useState(null);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
// Check if user is authenticated
const checkAuth = async () => {
try {
const token = localStorage.getItem('token');
if (token) {
const response = await fetch('/api/me', {
headers: { Authorization: `Bearer ${token}` }
});
if (response.ok) {
const userData = await response.json();
setUser(userData);
}
}
} catch (error) {
console.error('Auth check failed:', error);
} finally {
setLoading(false);
}
};
checkAuth();
}, []);
if (loading) {
return <div>Checking authentication...</div>;
}
if (!user) {
return <div>Please log in to access this page</div>;
}
return <WrappedComponent {...props} user={user} />;
};
}
// Protected component
function Dashboard({ user }) {
return (
<div>
<h1>Welcome, {user.name}!</h1>
<p>This is your dashboard</p>
</div>
);
}
const ProtectedDashboard = withAuth(Dashboard);Advanced HOC Patterns
// Advanced HOC with composition and configuration
import React from 'react';
// HOC for error boundaries
function withErrorBoundary(WrappedComponent) {
return class ErrorBoundary 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 boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong</h2>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return <WrappedComponent {...this.props} />;
}
};
}
// HOC for performance monitoring
function withPerformanceMonitoring(WrappedComponent, componentName) {
return function PerformanceMonitoredComponent(props) {
const startTime = React.useRef(Date.now());
React.useEffect(() => {
const endTime = Date.now();
const renderTime = endTime - startTime.current;
console.log(`${componentName} rendered in ${renderTime}ms`);
});
return <WrappedComponent {...props} />;
};
}
// HOC for data caching
function withCache(WrappedComponent, cacheKey) {
return function CachedComponent(props) {
const [cachedData, setCachedData] = React.useState(null);
React.useEffect(() => {
const cached = localStorage.getItem(cacheKey);
if (cached) {
try {
setCachedData(JSON.parse(cached));
} catch (error) {
console.error('Failed to parse cached data:', error);
}
}
}, []);
const updateCache = (data) => {
localStorage.setItem(cacheKey, JSON.stringify(data));
setCachedData(data);
};
return (
<WrappedComponent
{...props}
cachedData={cachedData}
updateCache={updateCache}
/>
);
};
}
// HOC composition
function compose(...hocs) {
return (Component) => {
return hocs.reduce((acc, hoc) => hoc(acc), Component);
};
}
// Usage with multiple HOCs
const EnhancedComponent = compose(
withErrorBoundary,
(Component) => withPerformanceMonitoring(Component, 'UserList'),
(Component) => withCache(Component, 'user-list-cache')
)(UserList);
// HOC with configuration
function withConfig(config) {
return function(WrappedComponent) {
return function ConfiguredComponent(props) {
return (
<WrappedComponent
{...props}
config={config}
/>
);
};
};
}
// Usage
const UserListWithConfig = withConfig({
pageSize: 10,
sortBy: 'name',
enableSearch: true
})(UserList);