Higher-Order Components (HOC)
Learn what a Higher-Order Component is, how to build one, and why the pattern has become less common now that custom Hooks exist.
Introduction
As React applications grow, you often find several components that need the same behavior — one component needs to show a spinner while data loads, another needs the same loading logic, and a third needs it too. Copy-pasting that logic into every component quickly becomes unmaintainable.
Higher-Order Components (HOCs) were one of the earliest patterns the React community adopted to solve this: a way to package up reusable logic and "wrap" it around any component that needs it, without changing that component's own code.
- What a Higher-Order Component is and how it works.
- How to build a withLoading HOC from scratch.
- The with... naming convention and why it exists.
- How to compose multiple HOCs together.
- Why custom Hooks have largely replaced HOCs in modern React code.
What is a Higher-Order Component?
A Higher-Order Component is a function that takes a component as an argument and returns a new, enhanced component. It does not modify the original component — it wraps it, adding extra props, behavior, or rendering logic around it.
The term comes from "higher-order function" in JavaScript — a function that takes or returns another function. A HOC applies that same idea to components: it is simply a function whose input and output are both components.
function withSomething(WrappedComponent) {
return function EnhancedComponent(props) {
// add extra logic, state, or props here
return <WrappedComponent {...props} />;
};
}A Function, Not a Component
A HOC itself is not rendered — it is a function you call that produces a component.
Wraps, Does Not Modify
The original component is left untouched; the HOC returns a brand-new wrapper component.
Reuses Logic
The same enhancement (loading, auth checks, theming) can be applied to any component.
Composable
Multiple HOCs can be layered on top of one another to combine behaviors.
Building a withLoading HOC
Let's build a practical example: a HOC that shows a spinner whenever a loading prop is true, and otherwise renders the wrapped component as normal. This is a classic use case — many components that fetch data need exactly this behavior.
function withLoading(WrappedComponent) {
return function WithLoading(props) {
if (props.loading) {
return (
<div className="spinner-container">
<div className="spinner" />
<p>Loading...</p>
</div>
);
}
return <WrappedComponent {...props} />;
};
}Now we can enhance any component with loading behavior, without touching its original code:
function UserProfile({ user }) {
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
const UserProfileWithLoading = withLoading(UserProfile);
function App() {
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser().then((data) => {
setUser(data);
setLoading(false);
});
}, []);
return <UserProfileWithLoading loading={loading} user={user} />;
}Loading...
(once fetchUser resolves)
Jane Doe
jane@example.comUserProfile never had to know anything about loading state. withLoading added that behavior from the outside, and {...props} passed everything else straight through untouched.
Naming Convention
By convention, HOCs are named starting with with — withLoading, withAuth, withRouter, withTheme. This makes it immediately clear, just from the name, that the function wraps a component rather than being a component itself.
- Name the HOC function itself with a with... prefix, e.g. withLoading.
- Give the returned wrapper component a readable displayName for easier debugging in React DevTools.
- Keep the HOC's added prop names distinct from props the wrapped component already expects, to avoid collisions.
function withLoading(WrappedComponent) {
function WithLoading(props) {
if (props.loading) return <p>Loading...</p>;
return <WrappedComponent {...props} />;
}
WithLoading.displayName = `WithLoading(${WrappedComponent.name})`;
return WithLoading;
}Composing Multiple HOCs
Because a HOC just takes a component and returns a component, HOCs can be nested to combine several behaviors — for example, adding both loading and authentication checks to the same component.
const EnhancedProfile = withAuth(withLoading(UserProfile));
// Equivalent to:
// withAuth(withLoading(UserProfile))
// which renders:
// <Auth check> -> <Loading check> -> <UserProfile />Wrapping a component in several HOCs creates several extra layers in the React DevTools component tree ("wrapper hell"), which can make debugging harder as the chain grows.
HOCs vs Custom Hooks
Before Hooks existed (pre-2019), HOCs — along with render props — were the primary way to share stateful logic between components. Since custom Hooks were introduced, most new code shares logic with a Hook instead, because Hooks avoid the extra wrapper components and prop-name collisions that HOCs can introduce.
HOC (older pattern)
- function withLoading(Component) {
- return function (props) {
- if (props.loading) return <Spinner />;
- return <Component {...props} />;
- };
- }
- const Enhanced = withLoading(Profile);
Custom Hook (modern equivalent)
- function useLoading(initial) {
- const [loading, setLoading] = useState(initial);
- return { loading, setLoading };
- }
- function Profile() {
- const { loading } = useLoading(true);
- if (loading) return <Spinner />;
- }
You will still encounter HOCs in some popular libraries and in legacy codebases — for example, older versions of libraries that provide connect() or withRouter() — so it is worth being able to read and understand them, even if you rarely write new ones.
Common Mistakes
- Forgetting to spread {...props} onto the wrapped component, which silently drops props the caller passed in.
- Creating the HOC-wrapped component inside another component's render/function body, which creates a brand-new component type on every render and remounts the tree.
- Mutating the original WrappedComponent instead of returning a new wrapper component.
- Overusing HOCs for simple logic that a custom Hook would express more directly with less nesting.
Best Practices
- Define HOCs outside of any component function, at module scope, so they are not recreated on every render.
- Follow the with... naming convention for clarity.
- Set a displayName on the wrapper for easier debugging.
- Pass through all unrelated props with {...props}.
- For new code, prefer a custom Hook unless you specifically need to wrap JSX output (which Hooks cannot do).
Frequently Asked Questions
Is a Higher-Order Component the same as a component?
No. A HOC is a function that returns a component. You call the HOC once (usually at module scope) to produce a new component, and then render that new component like any other.
Why do HOC names start with with?
It is purely a naming convention to signal intent — seeing withLoading(Profile) immediately tells you Profile is being wrapped with loading behavior.
Are HOCs deprecated?
No, they are not deprecated and still work fine. They are simply less commonly written in new code because custom Hooks usually solve the same problem more simply.
Can a HOC add new props to the wrapped component?
Yes. A common use is injecting extra props — for example withRouter used to inject routing props like location and history into the wrapped component.
Should I rewrite old HOCs I find in a codebase?
Not necessarily. If they work and are well-tested, leave them. Consider migrating to Hooks only when you are already touching that code for another reason.
Key Takeaways
- A Higher-Order Component is a function that takes a component and returns a new, enhanced component.
- HOCs are a pattern for reusing component logic, like the withLoading example that shows a spinner based on a loading prop.
- By convention, HOC functions are named starting with with....
- HOCs can be composed by nesting, e.g. withAuth(withLoading(Component)).
- Custom Hooks have largely replaced HOCs for new code, though HOCs still appear in some libraries and legacy code.
Summary
Higher-Order Components let you package up reusable behavior — like showing a loading spinner — into a function that wraps any component that needs it. You built a withLoading HOC, saw the with... naming convention, and compared HOCs to the more modern custom Hook approach.
Next, you will look at Render Props, another pre-Hooks pattern for sharing logic between components, and see how it compares to both HOCs and custom Hooks.
- You understand what a Higher-Order Component is.
- You built a working withLoading HOC.
- You know the with... naming convention.
- You can explain why Hooks have largely replaced HOCs.