![]()
Higher-Order Components (HOCs) are advanced React patterns for reusing component logic. Here are the key best practices to follow when implementing HOCs:
1. Naming Conventions
- Prefix HOC names: Use a prefix like
with(e.g.,withAuth,withLoading) - Display name: Set a proper display name for debugging
function withExample(Component) {
function WithExample(props) {
/* ... */
}
// Set display name
WithExample.displayName = `WithExample(${Component.displayName || Component.name || 'Component'})`;
return WithExample;
}
2. Pass Unrelated Props Through
Always pass down props that aren’t consumed by the HOC:
function withData(WrappedComponent) {
return function WithData(props) {
const [data, setData] = useState(null);
// Filter out HOC-specific props and pass the rest
const { extraProp, ...passThroughProps } = props;
return (
<WrappedComponent
data={data}
{...passThroughProps}
/>
);
}
}
3. Don’t Mutate the Original Component
Instead of modifying the input component, create a new one:
// Bad - Mutates original component
function badHOC(Component) {
Component.defaultProps = { /* ... */ };
return Component;
}
// Good - Creates new component
function goodHOC(Component) {
return function EnhancedComponent(props) {
return <Component {...props} />;
}
}
4. Maximize Composability
Design HOCs to work well when composed together:
// Can be composed like this:
const EnhancedComponent = withRouter(
withAuth(
withLoading(MyComponent)
)
);
// Or using compose utility (from Redux, Ramda, etc.):
const enhance = compose(
withRouter,
withAuth,
withLoading
);
const EnhancedComponent = enhance(MyComponent);
5. Use Static Methods Correctly
Copy static methods from wrapped component:
function withFeature(WrappedComponent) {
class WithFeature extends React.Component {
/* ... */
}
// Copy static methods
const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
WithFeature.displayName = `WithFeature(${wrappedComponentName})`;
WithFeature.staticMethod = WrappedComponent.staticMethod;
return WithFeature;
}
// Or use hoist-non-react-statics package
import hoistNonReactStatic from 'hoist-non-react-statics';
function withFeature(WrappedComponent) {
class WithFeature extends React.Component {
/* ... */
}
hoistNonReactStatic(WithFeature, WrappedComponent);
return WithFeature;
}
6. Forward Refs
Use React.forwardRef to maintain ref access to wrapped components:
function withLogging(WrappedComponent) {
class WithLogging extends React.Component {
/* ... */
}
return React.forwardRef((props, ref) => {
return <WithLogging {...props} forwardedRef={ref} />;
});
}
7. Type Safety with TypeScript
When using TypeScript, properly type your HOCs:
interface WithLoadingProps {
isLoading: boolean;
}
function withLoading<P extends object>(
Component: React.ComponentType<P>
): React.ComponentType<P & WithLoadingProps> {
return function WithLoading({ isLoading, ...props }: P & WithLoadingProps) {
return isLoading ? <Spinner /> : <Component {...props as P} />;
};
}
8. Document Expected Props
Clearly document any props the HOC injects or requires:
/**
* HOC that provides authentication
* @param {React.Component} WrappedComponent - Component to enhance
* @returns {React.Component} - New component with these props:
* - isAuthenticated {boolean}
* - user {object}
* - login {function}
* - logout {function}
*/
function withAuth(WrappedComponent) {
/* implementation */
}
9. Performance Considerations
- Avoid creating HOCs inside render methods (creates new component types each render)
- Implement shouldComponentUpdate/PureComponent/memo where appropriate
- Consider using hooks instead for simpler cases (HOCs can be replaced with custom hooks in many situations)
10. Testing
Make sure to test both:
- The HOC itself
- Components wrapped with the HOC
// Test example
test('withLoading shows spinner when loading', () => {
const TestComponent = () => <div>Content</div>;
const WrappedComponent = withLoading(TestComponent);
const { getByTestId } = render(<WrappedComponent isLoading={true} />);
expect(getByTestId('spinner')).toBeInTheDocument();
});
When to Use HOCs vs Hooks
While Hooks have replaced many HOC use cases, HOCs are still useful when:
- You need to manipulate the rendered component (e.g., adding wrappers)
- You need to maintain compatibility with class components
- You’re working with existing codebases that heavily use HOCs
Remember that HOCs create a layer of indirection, so prefer custom hooks when possible for simpler logic reuse.
