I’ve been diving deep into React patterns, organizing my thoughts in Notion, and Higher-Order Components (HOCs) have truly been a game-changer! 🌟 They help you reuse code and keep your app clean and organized. In this post, I’ll break down what HOCs are, share simple examples, and explore real-world use cases—all in an easy-to-follow way. Whether you’re new to React or leveling up your skills, you’ll see why HOCs are worth learning. Let’s dive in! 🎉
What’s an HOC in React? 🤔
A Higher-Order Component (HOC) is a function that takes a component and returns a new one with extra features. Think of it as upgrading a plain bike to an electric one—it’s still a bike, but now it does more! 🚴♂️⚡
Here’s the basic idea:
- Start with a component, like a button or form. 🔘
- The HOC wraps it, adding features like data fetching or user authentication. 🔄
- You get a new component that’s more powerful without modifying the original. 💪
Why Use HOCs?
- Save Time: Avoid repeating code across components. 🔄
- Stay Clean: Keep your components focused and tidy. 🧼
- Share Logic: Reuse the same logic across your app. 🔗
Let’s see an HOC in action with some practical examples. 👀
Example 1: Debugging Props with an HOC 🐞
When I started with React, I struggled to track props, often wasting time on typos. So, I built an HOC to log props to the console—a simple way to debug without cluttering my components! 🛠️
Code: checkProps.js
// HOC to log props for debugging
export const checkProps = (Component) => {
// Returns a new component
return (props) => {
// Show props in console
console.log("Props received:", props);
// Pass all props to the original component
return <Component {...props} />;
};
};
Usage: App.js
import { checkProps } from "./checkProps"; // Import HOC
import { UserInfo } from "./UserInfo"; // Import component
// Wrap UserInfo with HOC
const UserInfoWrapper = checkProps(UserInfo);
function App() {
return (
<div>
{/* Use wrapped component with props */}
<UserInfoWrapper name="John" age={23} />
</div>
);
}
export default App;
Component: UserInfo.js
// Simple component to show user info
export const UserInfo = ({ name, age }) => (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
</div>
);
How It Works
-
checkProps
takes a component (likeUserInfo
). - It returns a new component that:
- Logs the props (e.g.,
{ name: "John", age: 23 }
) to the console. - Passes all props to
UserInfo
using{...props}
.
- Logs the props (e.g.,
- In
App.js
, we useUserInfoWrapper
instead ofUserInfo
. - Open your console, and you’ll see the props printed!
Why It’s Useful
No more adding console.log
everywhere! This HOC lets you debug props for any component, keeping your code clean. 🧹 I used to typo prop names all the time—this HOC helped me spot mistakes fast. Try it for quick debugging! ⏱️
Example 2: Fetching Data with an HOC 📡
HOCs are awesome for handling data, like fetching user info from a server. This example shows an HOC that pulls user details and lets you edit them in a form—practical and reusable! 🔄
Code: includeUpdatableUser.js
import { useEffect, useState } from "react";
import axios from "axios"; // For server requests
// HOC to fetch and edit user data
export const includeUpdatableUser = (Component, userId) => {
return (props) => {
// Store original user data
const [user, setUser] = useState(null);
// Store editable user data
const [updatableUser, setUpdatableUser] = useState(null);
// Fetch data when component loads
useEffect(() => {
(async () => {
const response = await axios.get(`/users/${userId}`);
setUser(response.data); // Save original
setUpdatableUser(response.data); // Save editable
})();
}, []); // Run once
// Update data when user types
const userChangeHandler = (updates) => {
setUpdatableUser({ ...updatableUser, ...updates });
};
// Save changes to server
const userPostHandler = async () => {
const response = await axios.post(`/users/${userId}`, {
user: updatableUser,
});
setUser(response.data); // Update original
setUpdatableUser(response.data); // Update editable
};
// Reset to original data
const resetUserHandler = () => {
setUpdatableUser(user);
};
// Pass data and functions to component
return (
<Component
{...props}
updatableUser={updatableUser}
changeHandler={userChangeHandler}
userPostHandler={userPostHandler}
resetUserHandler={resetUserHandler}
/>
);
};
};
Usage: UserInfoForm.js
import { includeUpdatableUser } from "./includeUpdatableUser";
// Wrap form with HOC
export const UserInfoForm = includeUpdatableUser(
({ updatableUser, changeHandler, userPostHandler, resetUserHandler }) => {
// Get name and age, or empty if null
const { name, age } = updatableUser || {};
// Show form or loading
return updatableUser ? (
<div>
<label>
Name:
<input
value={name}
onChange={(e) => changeHandler({ name: e.target.value })}
/>
</label>
<br />
<label>
Age:
<input
value={age}
onChange={(e) => changeHandler({ age: Number(e.target.value) })}
/>
</label>
<br />
<button onClick={resetUserHandler}>Reset</button>
<button onClick={userPostHandler}>Save</button>
</div>
) : (
<h3>Loading...</h3>
);
},
"3" // User ID
);
function App() {
return <UserInfoForm />;
}
export default App;
How It Works
-
includeUpdatableUser
takes a component anduserId
. - It fetches data (e.g.,
{ name: "John", age: 23 }
) from/users/${userId}
. - It uses two states:
-
user
: Original data. -
updatableUser
: Editable copy.
-
- It passes four props to the component:
-
updatableUser
: Data to display. -
changeHandler
: Updates data on input. -
userPostHandler
: Saves changes to the server. -
resetUserHandler
: Restores the original data.
-
-
UserInfoForm
renders a form to edit the user’s name and age, with buttons to save or reset.
Why It’s Useful
This HOC handles all data tasks—fetching, editing, saving—so your form stays focused on the UI. You can reuse it for any user by swapping the userId
. My app crashed once without a loading state, but adding <h3>Loading...</h3>
fixed it. Always cover loading cases! ⏳
Example 3: A Reusable HOC for Any Resource 🔄
The user HOC was cool, but I wanted something more flexible—for products, posts, or any data. This generic HOC cuts down on repetitive code and works for any resource! ✂️
Code: includeUpdatableResource.js
import { useEffect, useState } from "react";
import axios from "axios";
// Capitalize names (e.g., "product" -> "Product")
const toCapital = (str) => str.charAt(0).toUpperCase() + str.slice(1);
// HOC for any resource
export const includeUpdatableResource = (Component, resourceUrl, resourceName) => {
return (props) => {
// Original data
const [data, setData] = useState(null);
// Editable data
const [updatableData, setUpdatableData] = useState(null);
// Fetch data on load
useEffect(() => {
(async () => {
const response = await axios.get(resourceUrl);
setData(response.data);
setUpdatableData(response.data);
})();
}, []); // Run once
// Update editable data
const changeHandler = (updates) => {
setUpdatableData({ ...updatableData, ...updates });
};
// Save to server
const dataPostHandler = async () => {
const response = await axios.post(resourceUrl, {
[resourceName]: updatableData,
});
setData(response.data);
setUpdatableData(response.data);
};
// Reset to original
const resetHandler = () => {
setUpdatableData(data);
};
// Dynamic props (e.g., product, onChangeProduct)
const resourceProps = {
[resourceName]: updatableData,
[`onChange${toCapital(resourceName)}`]: changeHandler,
[`onSave${toCapital(resourceName)}`]: dataPostHandler,
[`onReset${toCapital(resourceName)}`]: resetHandler,
};
// Pass props to component
return <Component {...props} {...resourceProps} />;
};
};
Usage: ProductForm.js
import { includeUpdatableResource } from "./includeUpdatableResource";
// Wrap product form with HOC
export const ProductForm = includeUpdatableResource(
({ product, onChangeProduct, onSaveProduct, onResetProduct }) => {
const { name, price } = product || {};
return product ? (
<div>
<label>
Product Name:
<input
value={name}
onChange={(e) => onChangeProduct({ name: e.target.value })}
/>
</label>
<br />
<label>
Price:
<input
value={price}
onChange={(e) => onChangeProduct({ price: Number(e.target.value) })}
/>
</label>
<br />
<button onClick={onResetProduct}>Reset</button>
<button onClick={onSaveProduct}>Save</button>
</div>
) : (
<h3>Loading...</h3>
);
},
"/products/1", // Product URL
"product" // Resource name
);
function App() {
return <ProductForm />;
}
export default App;
How It Works
-
includeUpdatableResource
takes:-
Component
: The component to wrap. -
resourceUrl
: Data source (e.g.,/products/1
). -
resourceName
: Name like"product"
.
-
- It fetches data and stores it in
data
(original) andupdatableData
(editable). - It creates dynamic props like
product
,onChangeProduct
,onSaveProduct
, andonResetProduct
. -
ProductForm
uses these props to edit a product’s name and price.
Why It’s Useful
This HOC works for any data—users, products, posts, you name it! Just change the URL and resource name. It’s a one-size-fits-all tool for fetching and editing. 🛠️ The dynamic props with toCapital
make it feel polished. Test it with different resources to see its power! 💪
Real-World HOC Use Cases 🌍
HOCs aren’t just for fetching data—they solve all kinds of real-world problems. Here are three common use cases with examples to inspire you! 💡
1. Securing Pages with Authentication 🔒
Want to restrict a page to logged-in users or admins? An HOC can handle that for you.
Code: withAuth.js
import { useAuth } from "./useAuth"; // Get user info
import Redirect from "./Redirect"; // Redirect component
import AccessDenied from "./AccessDenied"; // Error component
// HOC to check login and role
export const withAuth = (requiredRole) => (Component) => (props) => {
const { user } = useAuth();
// No user? Go to login
if (!user) {
return <Redirect to="/login" />;
}
// Wrong role? Show error
if (requiredRole && user.role !== requiredRole) {
return <AccessDenied />;
}
// All clear? Show component
return <Component {...props} user={user} />;
};
Usage
import { withAuth } from "./withAuth";
// Admin-only dashboard
const AdminDashboard = withAuth("admin")(({ user }) => (
<div>
<h2>Welcome, {user.name}!</h2>
<p>Admin Dashboard</p>
</div>
));
function App() {
return <AdminDashboard />;
}
export default App;
Why It’s Useful
This HOC protects pages without cluttering your components with login checks. Reuse it for any restricted page!
2. Tracking Page Visits 📈
Need to track when users visit a page? An HOC can log it automatically.
Code: withTracking.js
import { useEffect } from "react";
import analytics from "./analytics"; // Fake analytics tool
// HOC to track page views
export const withTracking = (eventName) => (Component) => (props) => {
// Track load/unload
useEffect(() => {
analytics.trackPageView(eventName); // Log visit
return () => {
analytics.trackPageExit(eventName); // Log exit
};
}, []); // Run once
// Show component
return <Component {...props} />;
};
Usage
import { withTracking } from "./withTracking";
// Track checkout page
const TrackedCheckout = withTracking("checkout_page")(() => (
<div>
<h2>Checkout</h2>
<input placeholder="Card number" />
<button>Pay</button>
</div>
));
function App() {
return <TrackedCheckout />;
}
export default App;
Why It’s Useful
This HOC adds tracking without touching your component’s code. It’s perfect for understanding user behavior!
3. Adding Themes to Components 🎨
Want to add light or dark mode to your app? An HOC can pass theme styles to components.
Code: withTheme.js
import { useTheme } from "./useTheme"; // Get theme info
// HOC to add theme
export const withTheme = (Component) => (props) => {
const theme = useTheme(); // E.g., { color: "black" }
// Pass theme to component
return <Component {...props} theme={theme} />;
};
Usage
import { withTheme } from "./withTheme";
// Themed button
const ThemedButton = withTheme(({ theme }) => (
<button style={{ background: theme.color, color: "white" }}>
Click Me
</button>
));
function App() {
return <ThemedButton />;
}
export default App;
Why It’s Useful
This HOC keeps components styled consistently. Use it for buttons, cards, or anything that needs a theme!
Why Use HOCs? 🤔
HOCs solve real problems and make coding easier. Here’s why I love them:
- Separation of Concerns: Keep logic (like fetching) separate from UI (like forms). 🧩
- Code Reuse: Write logic once, use it in many components. 🔄
- Testability: Test HOC logic alone, not mixed with UI. 🧪
- Clean Components: Avoid stuffing components with hooks or side effects. 🧼
HOCs made my code feel organized, like sorting my desk—they’re worth the effort! 🗂️
When to Use HOCs vs. Other Patterns? 🤔
HOCs aren’t always the answer. Here’s a quick guide to pick the right tool:
- Reuse logic across many components: Use an HOC or custom hook. 🔄
- Need lifecycle logic + rendering: HOC is the way to go. 🔄
- Small logic reuse (state, effect): Use a custom hook. 🔄
- Full control inside JSX: Avoid HOCs here. 🚫
Modern React often leans toward custom hooks for simple cases, but HOCs shine for wrapping behavior—like logging, auth, analytics, or conditional rendering. I tried hooks for everything at first, but HOCs were better for big logic like auth. Know both to choose wisely! 🧠
Final Tip: Make HOCs Composable 🧩
HOCs are like building blocks—you can stack them to add more features! 🏗️
Example
import { withAuth } from "./withAuth";
import { withTracking } from "./withTracking";
const MyComponent = () => <div>My App</div>;
// Chain HOCs
export default withAuth("admin")(withTracking("home_page")(MyComponent));
Or use a library like Redux for cleaner chaining:
import { compose } from "redux";
export default compose(withAuth("admin"), withTracking("home_page"))(MyComponent);
Why It’s Useful
Composability lets you mix and match HOCs, like adding auth and tracking to one component. 🔄 Chaining HOCs was tricky at first, but it’s super powerful. Start with two and build up! 🚀
Let’s Recap 📝
HOCs are a clever way to reuse logic in React. They wrap components to add features like debugging, data fetching, or security, keeping your code clean and organized. My Notion notes taught me they’re not hard—just smart tools for building better apps. 💡
What’s your favorite React pattern? Let me know in the comments—I’d love to hear your thoughts! 💬
Top comments (2)
been messing with stuff like this too and its kinda underrated how much cleaner your apps get when you commit to good patterns over time. you think people stick with hocs long-term or move on to hooks mostly?
Nice, post