React Higher Order Components
Table of Contents + −
In the last lesson, React Compound Components, you built components that cooperate through context. This time we look at an older way to reuse logic across components: the higher order component, or HOC.
🤔 Why do higher order components exist?
Here’s the problem they were built for:
- Several components all need the same wrapper behavior, like a loading state or auth check.
- Copying that logic into each one is repetitive and easy to get out of sync.
- Before hooks existed, there was no clean way to share stateful logic between components.
- So people invented a pattern: a function that takes a component and gives back an enhanced version.
So an HOC is a way to reuse logic by wrapping a component, adding behavior around it, without copying that behavior everywhere.
🐍 What is a higher order component?
Let’s define it precisely, because the name sounds scarier than it is.
- A higher order component is a function that takes a component and returns a new, enhanced component.
- It’s not a component itself; it’s a function that makes one.
- The returned component renders the original, plus whatever extra behavior the HOC adds.
Where does the name come from?
- A “higher order function” is one that takes or returns another function.
- An HOC does the same thing, but with a component instead of a function.
Think of it like gift-wrapping: the wrap adds something around the gift, but the gift inside is unchanged.
The naming convention
HOCs are conventionally named with a with prefix, like withLoading or withAuth. That tells you at a glance it’s an HOC that adds some behavior.
🧩 Building a withLoading HOC
Let’s build a classic one: withLoading, which shows “Loading…” while a isLoading prop is true, and otherwise renders the wrapped component.
// the HOC: takes a component, returns an enhanced onefunction withLoading(WrappedComponent) { return function WithLoading({ isLoading, ...rest }) { if (isLoading) { return <p>Loading...</p>; } // not loading: render the original with its remaining props return <WrappedComponent {...rest} />; };}Read it carefully, because the shape is the whole idea.
withLoadingis a function that takesWrappedComponent.- It returns a new component,
WithLoading. - That new component checks
isLoading. If true, it shows the spinner text. - If false, it renders the original component, passing along the rest of the props with
{...rest}.
So the HOC adds the loading behavior around any component you give it, without that component knowing anything about loading.
🖥️ Using the HOC
Now you wrap a normal component with the HOC to get an enhanced version that handles loading for free.
// a plain component that just shows a listfunction UserList({ users }) { return ( <ul> {users.map((u) => ( <li key={u.id}>{u.name}</li> ))} </ul> );}
// enhance it with loading behaviorconst UserListWithLoading = withLoading(UserList);
// use the enhanced versionfunction App() { return <UserListWithLoading isLoading={true} users={[]} />;}Walk through what happens.
UserListis a plain component that doesn’t know about loading.withLoading(UserList)producesUserListWithLoading, which does handle loading.- When
isLoadingistrue, it shows “Loading…”; when false, it shows the user list. - You could wrap any other component the same way to give it the same loading behavior.
Output
(isLoading = true)Loading...
(isLoading = false, with users)AlexRiya🆚 HOCs vs custom hooks
Here’s the honest, important part. Custom hooks have largely replaced HOCs for sharing logic, because they’re simpler.
- Custom hooks share stateful logic without wrapping components, so there’s no extra layer in the tree.
- HOCs add a wrapper component for each enhancement, which can stack up and make debugging harder.
- Hooks avoid problems HOCs had, like name clashes between props from different HOCs.
- So for new code, a custom hook (like
useLoading) is usually the cleaner choice.
So why learn HOCs at all? Because plenty of existing code and libraries still use them, and you need to recognize and work with them.
Prefer hooks for new code
HOCs aren’t wrong, but for new logic-sharing, custom hooks are simpler and avoid HOC pitfalls like wrapper nesting and prop name clashes. Learn HOCs to read existing code; reach for custom hooks when writing your own.
⚠️ Common Mistakes
A common mistake is forgetting to pass the remaining props through to the wrapped component.
// ❌ drops the other props - WrappedComponent gets nothing usefulreturn <WrappedComponent />;
// ✅ pass the rest of the props alongreturn <WrappedComponent {...rest} />;Keep these in mind.
- Don’t forget
{...rest}. The wrapped component still needs its own props, likeusers. - Don’t confuse an HOC with a component. An HOC is a function that returns a component.
- Don’t reach for HOCs in new code when a custom hook would be simpler.
✅ Best Practices
A few habits for HOCs.
- Name them with a
withprefix, likewithLoadingorwithAuth, so their role is clear. - Always pass through the remaining props with
{...rest}. - Keep each HOC focused on one piece of added behavior.
- For new logic-sharing, prefer a custom hook; use HOCs mainly to understand existing code.
Recognize it in the wild
You may see HOCs like connect() from older Redux code, or withRouter. Knowing the pattern, a function that wraps a component to add behavior, makes that code easy to read.
🧩 What You’ve Learned
- ✅ A higher order component (HOC) is a function that takes a component and returns an enhanced one
- ✅ It reuses logic by wrapping a component, adding behavior around it
- ✅ A classic example is
withLoading, which shows a spinner whileisLoadingis true - ✅ Always pass the remaining props through with
{...rest} - ✅ HOCs are named with a
withprefix by convention - ✅ Custom hooks have largely replaced HOCs for new code, but you’ll still see HOCs in existing code
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What is a higher order component?
Why: An HOC is a function, not a component. It takes a component and returns a new one that adds some behavior around the original.
- 2
What does the withLoading HOC do?
Why: withLoading wraps a component and adds loading behavior: a spinner while isLoading is true, and the original component (with its props) when false.
- 3
Why must an HOC pass {...rest} to the wrapped component?
Why: The HOC consumes some props (like isLoading) but the wrapped component still needs the rest. Spreading {...rest} passes those remaining props through.
- 4
What has largely replaced HOCs for sharing logic in new code?
Why: Custom hooks share stateful logic without wrapping components, avoiding HOC pitfalls. HOCs are still worth knowing to read existing code and libraries.
🚀 What’s Next?
Now you can recognize and build higher order components. There’s one more classic logic-sharing pattern from the same era: render props. Next you’ll learn how a component can share what it knows by calling a function you pass it.