Higher-Order Components (HOCs) in React
Higher-Order Components (HOCs)
~14 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
A Higher-Order Component (HOC) is a FUNCTION that takes a component and returns a NEW, enhanced component – a pattern from the era BEFORE hooks (2015-2018), less common today but still found in some libraries and codebases. You SHOULD be able to recognize it, even though custom hooks are usually the better choice today.
The basic formula: a function that returns a component
function withSomething(WrappedComponent) {
return function EnhancedComponent(props) {
// extra logic here
return <WrappedComponent {...props} />;
};
}The "with..." naming convention (like connect() from classic Redux, which is ITSELF a HOC) signals: "this function enhances a component with something". If this already looks familiar: you've already seen the "function takes a component, returns a component" pattern with memo() from chapter 31 AND lazy() from "React for Beginners" chapter 20 – both are technically HOCs that React itself ships.
Building a practical HOC: withAuthGuard
We'll build a HOC equivalent of ProtectedRoute from "React for Beginners" chapter 19 – not to replace ProtectedRoute (which remains the better solution for route protection), but to show the HOC PATTERN on a realistic, understandable example:
import { Navigate } from 'react-router-dom';
import { useAuthStore } from '../store/authStore';
function withAuthGuard(WrappedComponent) {
function AuthGuarded(props) {
const user = useAuthStore((state) => state.user);
if (!user) {
return <Navigate to="/login" replace />;
}
return <WrappedComponent {...props} />;
}
// For better DevTools display and error messages -
// without this, EVERY wrapped component would show up in DevTools
// as a generic "AuthGuarded", no matter which component it is
AuthGuarded.displayName = `withAuthGuard(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
return AuthGuarded;
}
export default withAuthGuard;{{...props}} passes ALL incoming props through unchanged to the wrapped component – the HOC adds BEHAVIOR without changing the original component's interface. displayName is a detail many HOC tutorials skip, but it matters in practice: without it, React DevTools would only show the generic wrapper name, making debugging harder.
Wrapping AccountPage with the HOC (purely for demonstration)
// At the end of src/pages/AccountPage.jsx, instead of "export default AccountPage;":
import withAuthGuard from '../hocs/withAuthGuard';
export default withAuthGuard(AccountPage);Achtung: In OUR project, ProtectedRoute (chapter 19 of "React for Beginners") remains the actual solution used for route protection – withAuthGuard is shown here PURELY for learning purposes, not wired up in production. Using both at once would give you duplicate, redundant checks.
Why HOCs have fallen out of fashion
- Wrapper hell: multiple HOCs combined (
withAuthGuard(withLogging(withTheme(Component)))) create deeply nested component trees that are hard to read in DevTools. - Prop name collisions: two HOCs that both inject a prop named
datasilently overwrite each other with no warning. - Unclear prop origin: when reading a wrapped component, it's not immediately obvious WHICH props come from "outside" and which the HOC injected.
The modern replacement: custom hooks
Almost everything you'd once have built a HOC for can be solved more elegantly today with a custom hook (see "React for Beginners" chapter 12) – NO nesting, NO prop collisions, the component stays a SINGLE function:
// As a custom hook instead of a HOC:
function useAuthGuard() {
const user = useAuthStore((state) => state.user);
return user;
}
// Usage directly inside the component:
function AccountPage() {
const user = useAuthGuard();
if (!user) return <Navigate to="/login" replace />;
// ... rest of the component
}Tipp: So why this chapter at all? HOCs still show up in older codebases, some libraries (e.g. react-dnd's DragSource, Storybook decorators), and in interview questions ("What is a HOC, and why are hooks preferred today?") – you should be able to READ and UNDERSTAND the pattern, even if you'll rarely WRITE it yourself in new code.