Higher Order Components (HOC)
A Higher Order Component is a function that takes a component and returns a new enhanced component.
Basic HOC Pattern
jsx
1// HOC that adds loading state
2function withLoading(WrappedComponent) {
3 return function WithLoadingComponent({ isLoading, ...props }) {
4 if (isLoading) {
5 return <div className="spinner">Loading...</div>
6 }
7
8 return <WrappedComponent {...props} />
9 }
10}
11
12// Usage
13function UserList({ users }) {
14 return (
15 <ul>
16 {users.map(user => <li key={user.id}>{user.name}</li>)}
17 </ul>
18 )
19}
20
21const UserListWithLoading = withLoading(UserList)
22
23// In parent
24<UserListWithLoading isLoading={loading} users={users} />HOC with Authentication
jsx
1function withAuth(WrappedComponent) {
2 return function WithAuthComponent(props) {
3 const { user, loading } = useAuth()
4 const navigate = useNavigate()
5
6 useEffect(() => {
7 if (!loading && !user) {
8 navigate('/login')
9 }
10 }, [user, loading, navigate])
11
12 if (loading) {
13 return <LoadingSpinner />
14 }
15
16 if (!user) {
17 return null
18 }
19
20 return <WrappedComponent {...props} user={user} />
21 }
22}
23
24// Usage
25function Dashboard({ user }) {
26 return <h1>Welcome, {user.name}!</h1>
27}
28
29const ProtectedDashboard = withAuth(Dashboard)HOC with Data Fetching
jsx
1function withData(WrappedComponent, fetchData) {
2 return function WithDataComponent(props) {
3 const [data, setData] = useState(null)
4 const [loading, setLoading] = useState(true)
5 const [error, setError] = useState(null)
6
7 useEffect(() => {
8 const load = async () => {
9 try {
10 const result = await fetchData(props)
11 setData(result)
12 } catch (err) {
13 setError(err.message)
14 } finally {
15 setLoading(false)
16 }
17 }
18
19 load()
20 }, [])
21
22 if (loading) return <Loading />
23 if (error) return <Error message={error} />
24
25 return <WrappedComponent {...props} data={data} />
26 }
27}
28
29// Usage
30function UserProfile({ data }) {
31 return <div>{data.name}</div>
32}
33
34const UserProfileWithData = withData(
35 UserProfile,
36 (props) => fetch(`/api/users/${props.userId}`).then(r => r.json())
37)HOC with Theme
jsx
1function withTheme(WrappedComponent) {
2 return function WithThemeComponent(props) {
3 const theme = useTheme()
4
5 return <WrappedComponent {...props} theme={theme} />
6 }
7}
8
9// Or using forwardRef for refs
10function withTheme(WrappedComponent) {
11 const WithTheme = React.forwardRef((props, ref) => {
12 const theme = useTheme()
13 return <WrappedComponent {...props} ref={ref} theme={theme} />
14 })
15
16 WithTheme.displayName = `WithTheme(${WrappedComponent.displayName || WrappedComponent.name})`
17
18 return WithTheme
19}Composing HOCs
jsx
1// Multiple HOCs
2const EnhancedComponent = withAuth(withTheme(withLoading(MyComponent)))
3
4// Or using a compose utility
5function compose(...fns) {
6 return (component) => fns.reduceRight((acc, fn) => fn(acc), component)
7}
8
9const enhance = compose(
10 withAuth,
11 withTheme,
12 withLoading
13)
14
15const EnhancedComponent = enhance(MyComponent)HOC Best Practices
Pass Through Props
jsx
1function withHOC(WrappedComponent) {
2 return function HOCComponent(props) {
3 const additionalProps = { /* ... */ }
4
5 // ✅ Pass all props through
6 return <WrappedComponent {...props} {...additionalProps} />
7 }
8}Copy Static Methods
jsx
1import hoistNonReactStatics from 'hoist-non-react-statics'
2
3function withHOC(WrappedComponent) {
4 function HOCComponent(props) {
5 return <WrappedComponent {...props} />
6 }
7
8 // Copy static methods
9 hoistNonReactStatics(HOCComponent, WrappedComponent)
10
11 return HOCComponent
12}Display Name for Debugging
jsx
1function withHOC(WrappedComponent) {
2 function HOCComponent(props) {
3 return <WrappedComponent {...props} />
4 }
5
6 HOCComponent.displayName = `WithHOC(${
7 WrappedComponent.displayName || WrappedComponent.name || 'Component'
8 })`
9
10 return HOCComponent
11}HOC vs Hooks
Most HOC patterns can be replaced with hooks:
jsx
1// HOC
2const UserListWithLoading = withLoading(UserList)
3
4// Hook equivalent
5function UserList() {
6 const { data, loading } = useFetch('/api/users')
7
8 if (loading) return <Loading />
9
10 return <ul>{data.map(user => ...)}</ul>
11}When to use HOCs
- Wrapping with providers
- Adding lifecycle methods
- Injecting props from external sources
- Creating reusable wrappers
When to use Hooks
- Sharing stateful logic
- Side effects
- Context consumption
- Most new code (preferred approach)
HOCs are a powerful pattern for component composition!
