In the world of React programming, code optimization and reusability are always top priorities. Among countless design patterns, Higher-Order Component (HOC) emerges as a powerful and classic technique, helping developers abstract and share logic between components efficiently.
So what is HOC in React? How does it work and when should we use it? Let's explore this concept comprehensively through the article below!
What is HOC? An Intuitive Look
Imagine you have a t-shirt manufacturing factory. This factory takes a plain t-shirt (a component) and "decorates" it with new features like printing designs, adding pockets, or changing colors. The final product is still a t-shirt, but it has been upgraded with new characteristics.
HOC (Higher-Order Component) in React operates on a similar philosophy.
HOC is a function that takes a Component and returns a new Component that has been "enhanced."
More technically, HOC is an advanced design pattern in React that allows for component logic reuse. HOC is not part of the React API, but rather a pattern that emerged from React's functional programming nature.
The basic syntax of an HOC looks like this:
const enhancedComponent = higherOrderComponent(WrappedComponent)
Where:
higherOrderComponent
: Is our HOC function.WrappedComponent
: Is the original component we want to "wrap" or "enhance."enhancedComponent
: Is the new component that contains the HOC logic and rendersWrappedComponent
inside.
How does the "inside" of an HOC work?
To understand better, let's build a classic example: creating an HOC called withLoading
to display a "Loading..." message while the component is waiting for data.
Suppose we have a ProductList
component responsible for fetching and displaying a list of products.
// ProductList.js
import React, { useState, useEffect } from 'react'
const ProductList = () => {
const [products, setProducts] = useState([])
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// Simulate data fetching
setTimeout(() => {
setProducts(['Product 1', 'Product 2', 'Product 3'])
setIsLoading(false)
}, 2000)
}, [])
if (isLoading) {
return <div>Loading...</div>
}
return (
<ul>
{products.map((product) => (
<li key={product}>{product}</li>
))}
</ul>
)
}
export default ProductList
Now, if we have another component, such as UserList
, that also needs similar "Loading..." logic, repeating the isLoading
code is not optimal. This is exactly when HOC shines.
We'll create the withLoading
HOC:
// withLoading.js
import React from 'react'
const withLoading = (WrappedComponent) => {
// HOC returns a new class component or functional component
return class WithLoading extends React.Component {
render() {
// Receive an `isLoading` prop and decide what to render
const { isLoading, ...props } = this.props
if (isLoading) {
return <div>Loading data, please wait...</div>
}
// If not loading, render the original component with its props
return <WrappedComponent {...props} />
}
}
}
export default withLoading
Now, we can apply this HOC to any component:
// ProductListWithLoading.js
import withLoading from './withLoading'
import ProductList from './ProductList' // Original component no longer needs loading logic
const ProductListWithLoading = withLoading(ProductList)
export default ProductListWithLoading
In the parent component, we just need to pass the isLoading
prop:
// App.js
function App() {
const [loading, setLoading] = useState(true);
// ... logic for fetching data and setting loading...
return <ProductListWithLoading isLoading={loading} products={...} />;
}
As you can see, the withLoading
HOC has helped us completely separate the logic for displaying data loading states from the component, making the code clean, maintainable, and reusable.
Real-world applications of HOC
HOC is not just for displaying loading states. It's extremely flexible and can be used in many different scenarios:
- Props manipulation and control: Adding or modifying props passed to the wrapped component.
- State abstraction and management: HOC can manage state and pass it down to child components as props. For example: HOC managing the open/close state of a modal.
- Authentication and authorization: Wrapping routes or components with a
requireAuth
HOC to check if the user is logged in. If not, redirect them to the login page. - Store connection (Redux): The
connect
function fromreact-redux
is the most classic example of HOC. It takes a component and "injects" state from the Redux store and action creators into that component's props. - Logging and tracking: Creating an HOC to log lifecycle events or user interactions within a component.
- Context provision: HOC can read values from React Context and pass them as props to components that don't need to directly use
Context.Consumer
.
HOC vs. Hooks: When to use which?
The emergence of React Hooks (especially useState
, useEffect
, useContext
) has changed how we write components and reuse logic. Hooks provide a more direct and simpler approach than HOC in many cases.
So, have Hooks made HOC obsolete? The answer is no.
Higher-Order Components (HOC)
- Nature: A function that wraps a component.
- Usage:
const EnhancedComponent = withHOC(MyComponent);
- Advantages:
- Works well with both class components and functional components.
- Very powerful in abstracting complex logic and restructuring components.
- Disadvantages:
- Can create "wrapper hell."
- Difficult to track the origin of props (which props come from which HOC?).
- Syntax is somewhat more verbose than Hooks.
React Hooks
- Nature: Special functions that allow "hooking into" React features.
- Usage:
const value = useCustomHook();
- Advantages:
- Concise syntax, easy to read and understand.
- Avoids "wrapper hell" when multiple HOCs are nested.
- Logic is placed directly inside the component, easier to track.
- Disadvantages:
- Only works in functional components.
- Must follow Hooks rules (only call at top-level, don't call in loops/conditions).
When should you choose HOC?
- When you need to reuse the same logic for many class components.
- When you want to change how a component renders from the outside thoroughly (e.g., adding wrapper elements, completely changing DOM structure).
- When working with older libraries or large codebases that still widely use HOC (like older versions of
react-redux
).
When should you choose Hooks?
- In most cases of modern React application development with functional components.
- When you want to share stateful logic in a simple and direct way.
- To avoid the complexity of nesting multiple HOCs.
In reality, HOC and Hooks can coexist and complement each other perfectly in the same project.
Conclusion: HOC is not obsolete
Higher-Order Component (HOC) is a powerful design pattern and an important part of React's development history. Although the popularity of React Hooks has brought simpler alternatives for many problems, HOC still maintains a solid position in solving structured logic reuse issues, especially in complex scenarios or when working with class components.
Understanding HOC not only helps you master a powerful tool but also provides deeper insights into React's architecture and capabilities. Good luck applying this technique to your projects!