Higher-Order Components (HOCs)
Reviewed & published by Brayan K
A higher-order component is a function that takes a component as input and returns a new, enhanced component that wraps it — adding shared behavior like authentication, logging, or injected data without changing the original. Names like withAuth or withLogging are the classic convention.
Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- What an HOC is: component in, enhanced component out
- Wrapping a component to add behavior without editing it
- Forwarding props with the spread operator
- Setting displayName for readable DevTools output
- Building withAuth and withLogging examples
- Why hooks usually replace HOCs in new code
1️⃣ Component In, Enhanced Component Out
An HOC is just a function. It receives a component (Wrapped) and returns a brand-new component that renders Wrapped with extra props or behavior. The original is never modified — you compose enhancements around it.
function withGreeting(Wrapped) {
return function Enhanced(props) {
return <Wrapped {...props} greeting={"Hello, " + props.name + "!"} />;
};
}
const SmartCard = withGreeting(ProfileCard);
// <SmartCard name="Ada" /> renders ProfileCard with a greeting prop added// An HOC is a function: component IN, enhanced component OUT.
// We model components as functions that take props and return a string.
function withGreeting(Wrapped) {
return function Enhanced(props) { // a NEW component
const greeting = "Hello, " + props.name + "!";
return Wrapped({ ...props, greeting }); // add a prop, forward the rest
};
}
function Card(props) { return props.greeting + " (role: " + props.role + ")"; }
const SmartCard = withGreeting(Card); // enhance it
console.log(SmartCard({ name: "Ada", role: "admin" }));
// Hello, Ada! (role: admin)2️⃣ Always Forward the Props
The most common HOC bug is swallowing the props. The wrapper must spread {...props} onto the wrapped component so everything the caller passed reaches it, plus any props the HOC injects. Forget the spread and the inner component renders with nothing.
function withLogging(Wrapped) {
function Enhanced(props) {
useEffect(() => console.log("mounted", Wrapped.name), []);
return <Wrapped {...props} />; // forward EVERYTHING
}
return Enhanced;
}// The #1 HOC rule: FORWARD the props you didn't consume.
// Spread {...props} so the wrapped component still gets everything.
function withLogging(Wrapped) {
return function Enhanced(props) {
console.log("rendering with props:", JSON.stringify(props));
return Wrapped({ ...props }); // forward everything
};
}
function Button(props) { return "[" + props.label + "]"; }
const LoggedButton = withLogging(Button);
console.log(LoggedButton({ label: "Save", id: 7 }));
// rendering with props: {"label":"Save","id":7}
// [Save]3️⃣ displayName, Guards & vs Hooks
Set Enhanced.displayName so DevTools shows the wrapping clearly. HOCs can also conditionally render — a withAuth can short-circuit to a login prompt. For most new code a custom hook is simpler, but you'll still meet HOCs like connect() in existing apps.
function withAuth(Wrapped) {
function Enhanced(props) {
if (!props.user) return <LoginPrompt />; // guard
return <Wrapped {...props} />;
}
Enhanced.displayName = "withAuth(" + (Wrapped.displayName || Wrapped.name) + ")";
return Enhanced;
}// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// withAuth should render the wrapped component ONLY if props.user
// exists; otherwise return "Please log in". Forward the props.
function withAuth(Wrapped) {
return function Enhanced(props) {
if (___) return "Please log in"; // 👉 the guard: !props.user
return Wrapped({ ___ }); // 👉 forward props with ...props
};
}
function Dashboard(props) { return "Welcome " + props.user; }
const Protected = withAuth(Dashboard);
console.log(Protected({ user: "Ada" })); // Welcome Ada
console.log(Protected({})); // Please log in
// ✅ Expected output:
// Welcome Ada
// Please log in🧩 Reorder Challenge
These lines build a complete withAuth HOC. Put them in the right order:
A. if (!props.user) return <LoginPrompt />;
B. function withAuth(Wrapped) {
C. return <Wrapped {...props} />;
D. function Enhanced(props) {
E. } Enhanced.displayName = "withAuth"; return Enhanced; }Common Errors (and the fix)
- Wrapped component loses its props. — You didn't spread them. Always render <Wrapped {...props} />.
- DevTools shows "Unknown". — Set Enhanced.displayName so the wrapping is visible.
- Defining the HOC inside render. — A new wrapped component every render remounts the tree and loses state. Define HOCs at module scope.
- Stacking many HOCs ("wrapper hell"). — Hard to trace. Prefer a custom hook for new shared logic.
📋 Quick Reference
| Step | Code |
|---|---|
| Signature | function withX(Wrapped) { ... } |
| Return new component | return function Enhanced(props) {...} |
| Forward props | <Wrapped {...props} /> |
| Debug name | Enhanced.displayName = "withX(...)" |
🧠 Quick Recall
1. What goes in and what comes out of an HOC?
A component goes in; a new, enhanced component comes out. The original is never modified.
2. What's the most common HOC bug?
Forgetting to forward props. Always spread {...props} onto the wrapped component.
3. Why set displayName?
So React DevTools shows the wrapping clearly instead of "Unknown," making the tree easy to debug.
Mini-Challenge: withTimestamp
Build an HOC that injects a stamp prop, forwards the rest, and sets a readable displayName. Run it and check your output.
// 🎯 MINI-CHALLENGE: withTimestamp HOC
//
// Write withTimestamp(Wrapped) returning a new component that:
// 1. injects an extra prop "stamp" with the value "2026"
// 2. forwards all original props
// Also set Enhanced.displayName to "withTimestamp(Note)".
// Then render and log the result and the displayName.
//
// ✅ Expected output:
// Note @ 2026: hi
// withTimestamp(Note)
function Note(props) { return "Note @ " + props.stamp + ": " + props.text; }
// your code here🎉 Lesson Complete
- ✅ An HOC takes a component and returns an enhanced one
- ✅ Always forward props with {...props}
- ✅ Set displayName for readable DevTools output
- ✅ HOCs are legacy-but-common; prefer hooks for new shared logic
Practice quiz
What is a higher-order component (HOC)?
- A component that renders other components as children
- A built-in React hook
- A function that takes a component and returns a new, enhanced component
- A component with more than one root element
Answer: A function that takes a component and returns a new, enhanced component. An HOC is a function: a component goes in, and a new component that wraps it comes out.
Does an HOC modify the component it wraps?
- No, it composes around the original, leaving it unchanged
- Yes, it edits the original component's code
- Only in development mode
- Only if you pass a flag
Answer: No, it composes around the original, leaving it unchanged. The original is never modified; the HOC returns a new component that renders the original with added behavior.
What is the most common HOC bug?
- Returning the wrong type
- Using too many hooks
- Not exporting the component
- Forgetting to forward (spread) the props onto the wrapped component
Answer: Forgetting to forward (spread) the props onto the wrapped component. If you forget {...props}, the wrapped component loses everything the caller passed.
How do you forward props through an HOC?
- Pass them one by one manually
- Spread them: <Wrapped {...props} />
- Use props.forward()
- Call this.props inside the function
Answer: Spread them: <Wrapped {...props} />. Spreading {...props} onto the wrapped component passes everything through, plus any props the HOC injects.
Why set Enhanced.displayName on the returned component?
- So React DevTools shows the wrapping clearly instead of 'Unknown'
- To improve runtime performance
- Because React requires it to render
- To enable hot reloading
Answer: So React DevTools shows the wrapping clearly instead of 'Unknown'. A displayName makes the wrapping visible in DevTools, which makes the component tree easy to debug.
Where should an HOC be defined?
- Inside the component's render so it sees fresh props
- Inside a useEffect
- At module scope, not inside render
- Inside an event handler
Answer: At module scope, not inside render. Defining an HOC inside render creates a new component every render, remounting the tree and losing state. Define it at module scope.
By naming convention, an HOC that adds authentication is usually called what?
- AuthComponent
- withAuth
- useAuth
- AuthHOC
Answer: withAuth. The classic convention prefixes HOCs with 'with', e.g. withAuth or withLogging.
In modern React, what is usually preferred over writing a new HOC for shared logic?
- A class component
- A render prop
- A global variable
- A custom hook
Answer: A custom hook. A custom hook is typically cleaner and avoids deep wrapper nesting, though HOCs remain common in older codebases.
How can an HOC like withAuth render conditionally?
- It cannot render conditionally
- It can short-circuit, e.g. return a login prompt when there is no user
- Only by using a useEffect
- By throwing an error
Answer: It can short-circuit, e.g. return a login prompt when there is no user. An HOC can guard: if there is no user, it returns a login prompt instead of the wrapped component.
What problem arises from stacking many HOCs?
- The app stops compiling
- Props are automatically duplicated
- 'Wrapper hell' — deeply nested wrappers that are hard to trace
- Hooks stop working
Answer: 'Wrapper hell' — deeply nested wrappers that are hard to trace. Many stacked HOCs create deep nesting that is hard to debug; a custom hook is often a flatter alternative.
Continue this course
- Previous: Render Props Pattern
- Next: Checkpoint: Patterns & Modern Hooks — Build a reusable pattern with modern hooks — then a quiz
- Quick reference: React cheat sheet
Frequently asked questions
What is a higher-order component?
A higher-order component (HOC) is a function that takes a component and returns a new, enhanced component. It wraps the original to add behavior — authentication checks, logging, injected data — without modifying the original component's code.
What does 'forward the props' mean?
The wrapper component receives props and passes them through to the wrapped component with {...props}, usually adding a few of its own. If you forget to spread the props, the inner component loses everything the caller passed, which is a very common HOC bug.
Why set a displayName on the returned component?
Without it, React DevTools shows the wrapper as 'Unknown' or just the HOC name, making debugging hard. Convention is to set Enhanced.displayName = 'withAuth(' + getName(Wrapped) + ')' so the wrapping is visible in the tree.
Should I write new HOCs today?
Usually a custom hook is cleaner and avoids deep wrapper nesting. But HOCs are still common in older codebases and some libraries (connect, withRouter), so you need to read and maintain them even if you reach for hooks in new code.