Understanding Higher-Order Components (HOC)
A Higher-Order Component (HOC) is a function that takes a component and returns an enhanced component. HOCs are commonly used to add functionality to components without modifying them directly. This pattern is similar to the Decorator pattern in traditional programming and is useful for adding reusable logic across multiple components.
Example: Creating an HOC for Authorization
Consider an HOC that checks if a user is authenticated before allowing access to a component:
import React from 'react';
interface Props {
isAuthenticated: boolean;
}
const withAuthorization = (Component: React.FC) => (props: Props) => {
if (!props.isAuthenticated) {
return
;
}
return ;
};
// Base component
const Dashboard: React.FC = () => (
);
const ProtectedDashboard = withAuthorization(Dashboard);
// Usage
;
Here, `withAuthorization` wraps the `Dashboard` component and adds a check for `isAuthenticated`. If the user isn’t authenticated, it displays a login prompt instead. This HOC can be reused to add authorization logic to multiple components.
Understanding Render Props
Render Props is a pattern where a component’s child is a function that returns a React element. This approach allows components to share logic by passing it as a function, enabling more flexibility than traditional component composition.
Example: Using Render Props for Mouse Tracking
The following example demonstrates a Render Prop component that tracks the mouse position and passes it to its children as a function:
import React, { useState, useEffect } from 'react';
interface MousePositionProps {
render: (position: { x: number; y: number }) => JSX.Element;
}
const MousePosition: React.FC = ({ render }) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (event: MouseEvent) => {
setPosition({ x: event.clientX, y: event.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return render(position);
};
// Usage
const App = () => (
(
Mouse position: ({x}, {y})
)} />
);
In this example, `MousePosition` provides the `x` and `y` coordinates to its child as a function, allowing any component to access the current mouse position dynamically. This pattern is highly flexible, as you can define the UI within the `render` prop.
When to Use HOCs vs. Render Props
Both HOCs and Render Props serve similar purposes, but they are suited for different scenarios:
- HOCs: Useful for applying common logic across multiple components, such as authentication, logging, or error handling. HOCs are best suited when you need to add functionality that doesn’t require interaction with the component’s children.
- Render Props: Ideal for scenarios where you need to share data or state dynamically with a component’s children. Render Props offer more flexibility in defining what is rendered based on data passed to the function.
Using HOCs and Render Props Together
In some cases, HOCs and Render Props can be combined to leverage the benefits of both patterns. For example, a HOC can wrap a component to handle authentication, while a Render Prop can be used to provide the authenticated user’s data dynamically to children components:
import React from 'react';
interface AuthContextProps {
isAuthenticated: boolean;
user: { name: string; role: string } | null;
}
const AuthContext = React.createContext(undefined);
const withAuth = (Component: React.FC) => (props: any) => (
{auth => (auth && auth.isAuthenticated ? :
)}
);
interface UserInfoProps {
render: (user: { name: string; role: string }) => JSX.Element;
}
const UserInfo: React.FC = ({ render }) => (
{auth => (auth && auth.user ? render(auth.user) :
)}
);
// Usage
const Dashboard = () => (
(
Welcome, {user.name}! Role: {user.role}
)} />
);
const ProtectedDashboard = withAuth(Dashboard);
// Wrapping App with AuthContext Provider
const App = () => (
);
export default App;
In this example, `withAuth` acts as a HOC that protects the `Dashboard` component, while `UserInfo` uses Render Props to provide user data to its children. This approach allows for granular control over data access and component behavior.
Conclusion
Higher-Order Components and Render Props are powerful patterns in React that enable code reuse, flexibility, and improved organization. While HOCs are great for adding shared logic across components, Render Props are better suited for sharing state or data with children. By understanding and applying these patterns, developers can create scalable, maintainable, and highly customizable React applications.