Higher order components
Higher-Order Components (HOCs) are an advanced technique in React for reusing component logic. HOCs are not part of the React API per se, but rather a pattern that emerges from React's compositional nature. A Higher-Order Component is a function that takes a component and returns a new component with enhanced functionality.
Table of Contents
- What is a Higher-Order Component?
- Basic HOC Pattern
- HOC Examples
- Best Practices
- HOCs vs Hooks
- Conclusion
What is a Higher-Order Component?
const EnhancedComponent = higherOrderComponent(WrappedComponent);
A HOC is a pure function with zero side-effects that:
- Takes a React component as an argument
- Returns a new React component
- The returned component renders the original component with additional props or behavior
Basic HOC Pattern
Function Component HOC
import React from "react";
// Basic HOC structure
const withEnhancement = (WrappedComponent) => {
return (props) => {
// Add logic here
const enhancedProps = {
...props,
additionalProp: "enhanced value",
};
return <WrappedComponent {...enhancedProps} />;
};
};
// Usage
const MyComponent = ({ message, additionalProp }) => (
<div>
<p>{message}</p>
<p>{additionalProp}</p>
</div>
);
const EnhancedComponent = withEnhancement(MyComponent);
Class Component HOC (Legacy)
import React, { Component } from "react";
const withEnhancement = (WrappedComponent) => {
return class extends Component {
render() {
return <WrappedComponent {...this.props} additionalProp="enhanced" />;
}
};
};
HOC Examples
Data Fetching HOC
const withData = (url) => (WrappedComponent) => {
return (props) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch((error) => {
setError(error);
setLoading(false);
});
}, [url]);
return (
<WrappedComponent
{...props}
data={data}
loading={loading}
error={error}
/>
);
};
};
// Usage
const UserProfile = ({ data, loading, error }) => {
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Welcome, {data.name}!</div>;
};
const UserProfileWithData = withData("/api/user")(UserProfile);
Theme Provider HOC
const withTheme = (WrappedComponent) => {
return (props) => {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
return (
<div className={`theme-${theme}`}>
<WrappedComponent {...props} theme={theme} toggleTheme={toggleTheme} />
</div>
);
};
};
Best Practices
1. Use Display Names for Debugging
const withAuth = (WrappedComponent) => {
const AuthenticatedComponent = (props) => {
// HOC logic here
return <WrappedComponent {...props} />;
};
// Set display name for better debugging
AuthenticatedComponent.displayName = `withAuth(${
WrappedComponent.displayName || WrappedComponent.name
})`;
return AuthenticatedComponent;
};
2. Copy Static Methods
import hoistNonReactStatics from "hoist-non-react-statics";
const withEnhancement = (WrappedComponent) => {
const EnhancedComponent = (props) => {
return <WrappedComponent {...props} />;
};
// Copy static methods
hoistNonReactStatics(EnhancedComponent, WrappedComponent);
return EnhancedComponent;
};
3. Don't Use HOCs Inside Render
// ❌ Bad: Creates new component on every render
const MyComponent = () => {
const EnhancedComponent = withAuth(SomeComponent);
return <EnhancedComponent />;
};
// ✅ Good: Create enhanced component outside
const EnhancedComponent = withAuth(SomeComponent);
const MyComponent = () => {
return <EnhancedComponent />;
};
HOCs vs Hooks
With the introduction of React Hooks, many HOC patterns can be replaced with custom hooks:
HOC Pattern
const withCounter = (WrappedComponent) => {
return (props) => {
const [count, setCount] = useState(0);
return (
<WrappedComponent
{...props}
count={count}
increment={() => setCount((c) => c + 1)}
/>
);
};
};
Hook Pattern
const useCounter = (initialValue = 0) => {
const [count, setCount] = useState(initialValue);
const increment = () => setCount((c) => c + 1);
return { count, increment };
};
// Usage in component
const MyComponent = () => {
const { count, increment } = useCounter();
return <button onClick={increment}>Count: {count}</button>;
};
When to Use HOCs vs Hooks
Use HOCs when:
- You need to wrap components conditionally
- Working with class components
- Need to modify component behavior at the component level
Use Hooks when:
- Working with function components
- Need to share stateful logic
- Want more granular control over what gets shared
Conclusion
Higher-Order Components are a powerful pattern for code reuse in React applications. While hooks have become the preferred method for sharing stateful logic in modern React applications, HOCs still have their place, especially when working with class components or when you need to conditionally wrap components.
Remember to follow best practices, avoid common pitfalls, and consider whether a custom hook might be a better solution for your use case.
This documentation serves as a comprehensive guide to React HOCs. For more React patterns and best practices, refer to the official React documentation.
Quiz Time
1. What will be rendered when this HOC is used?
2. What props will MyComponent receive?
3. What will be rendered when isVisible is false?
4. What will be the final props received by BaseComponent?
5. How many times will 'Counter rendered' be logged when the button is clicked twice?
1. What will be rendered when this HOC is used?
2. What props will MyComponent receive?
3. What will be rendered when isVisible is false?
4. What will be the final props received by BaseComponent?
5. How many times will 'Counter rendered' be logged when the button is clicked twice?