Suggest an editImprove this articleRefine the answer for “What is HOC (higher-order component) in React”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**HOC (Higher-Order Component)** is a function that takes a React component and returns a new component with added behavior. ```jsx const withAuth = (WrappedComponent) => { function Authenticated(props) { const { user } = useContext(AuthContext); if (!user) return <Navigate to="/login" />; return <WrappedComponent {...props} user={user} />; } return Authenticated; }; const ProtectedDashboard = withAuth(Dashboard); ``` **Key point:** the original component is never modified. In modern React, custom hooks cover most of the same use cases with less wrapping overhead.Shown above the full answer for quick recall.Answer (EN)Image**HOC (Higher-Order Component)** is a function that takes a React component and returns a new component with added behavior, without touching the original. ## Theory ### TL;DR - HOC = a function that takes a component and returns a new, enhanced one - Analogy: pass a plain blender into the HOC, get back one that auto-measures ingredients before blending - Main point: adds behavior through composition, not by modifying source code - Used in: Redux `connect`, React Router `withRouter`, Material-UI `withStyles` - Decision rule: use HOC for cross-cutting concerns in class-heavy codebases; prefer custom hooks in new functional code ### Quick example ```jsx // withLoading: adds a spinner, no changes to UserList needed const withLoading = (WrappedComponent) => (props) => { if (props.isLoading) return <div>Loading...</div>; // intercepts before render return <WrappedComponent {...props} />; // passes all props through }; const UserList = ({ users }) => ( <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul> ); const UserListWithLoading = withLoading(UserList); // <UserListWithLoading isLoading={false} users={[{id:1, name:'Alice'}]} /> // Output: <ul><li>Alice</li></ul> ``` `withLoading` knows nothing about `UserList`. It checks one prop and delegates the rest. That separation is the whole point. ### Key difference: composition vs inheritance HOC is composition. You wrap a component externally, preserve its original render method, and inject extra props or logic via closure. The original component stays unchanged. Inheritance reaches inside and modifies the class, which breaks in React's concurrent mode and makes code harder to trace. With HOCs you can stack layers, remove them, and test each one in isolation. ### When to use - Shared data fetching across many components -> HOC like `withDataFetch` - Auth checks in a class-based codebase -> HOC; in new functional code, a custom hook is cleaner - Render-time logging or telemetry -> HOC wraps the render call directly, no hook overhead - Logic that only one component needs -> skip the HOC, inline it ### How React handles a HOC internally React treats the returned function as a standard component. On mount, it runs the outer HOC function, creating a closure over `WrappedComponent`. The wrapper captures props in its own lifecycle and delegates render to `<WrappedComponent />` via `React.createElement`. No special browser APIs are involved. It is plain JavaScript function composition that runs during reconciliation. ### Common mistakes **Mutating the wrapped component directly:** ```jsx // Wrong: modifies the original component const badHOC = (WC) => { WC.prototype.newMethod = () => {}; // pollutes the source return WC; }; ``` This breaks component purity and fails in StrictMode, which double-mounts components to catch side effects. Return a new component. Never modify the one you received. **Skipping displayName:** ```jsx // Wrong: React DevTools shows "Anonymous" const withData = (WC) => (props) => <WC {...props} />; ``` ```jsx // Correct const withData = (WC) => { const Enhanced = (props) => <WC {...props} />; Enhanced.displayName = `withData(${WC.displayName || WC.name})`; return Enhanced; }; ``` Without `displayName`, your DevTools stack shows `Anonymous^12`. Debugging becomes a guessing game. **Defining HOC inside render:** ```jsx // Wrong: new HOC instance on every render function App() { const UserListWithLogger = withLogger(UserList); // remounts every render return <UserListWithLogger users={data} />; } ``` The wrapped component unmounts and remounts each render, losing all local state. Define HOCs at module level, outside any component. **Losing static methods:** When you wrap a component, static methods on the original vanish. Copy them manually: ```jsx EnhancedComponent.staticMethod = WrappedComponent.staticMethod; ``` ### Real-world usage - Redux: `connect(mapStateToProps)(Component)` subscribes to the store and maps state to props - React Router v5: `withRouter` injects `location`, `history`, `match` into class components - Material-UI v4: `withStyles` and `withTheme` attach CSS-in-JS styles - Composing multiple HOCs: `withAuth(withData(withLogger(Dashboard)))` or `compose(withAuth, withData, withLogger)(Dashboard)` using lodash `flowRight` ### Follow-up questions **Q:** Write a HOC that fetches data and passes it as a prop. **A:** See the `withFetch` example below. A senior answer adds error handling, a loading state, and an `AbortController` to cancel the request on unmount. **Q:** How do you compose multiple HOCs? **A:** `withAuth(withData(Component))` or `compose(withAuth, withData)(Component)`. The outermost HOC runs first during render. **Q:** Why don't developers use HOCs as much anymore? **A:** Hooks solve the same cross-cutting concerns without wrapper stacks, prop collision issues, or ref forwarding complexity. On React 16.8+, a custom hook is almost always the simpler path. **Q:** HOC vs render props - what is the difference? **A:** HOC injects props automatically and returns an enhanced component; the data flow is implicit. Render props expose data through a function passed as `children`, which is explicit. Both work; render props give finer control over what gets rendered. **Q:** How do you test a HOC-wrapped component? **A:** Test the HOC and the wrapped component separately. Mock the HOC's dependencies, shallow-render the wrapper, and assert which props were injected. Or pull the logic into a custom hook and unit-test the hook directly. **Q:** How do refs behave inside a HOC? (Senior) **A:** Refs don't pass through automatically because `ref` is not a regular prop. Use `React.forwardRef` inside the HOC to forward it to the wrapped component. Without it, `ref` is `null` on the outer wrapper and any imperative handle breaks. ## Examples ### Basic: logger HOC ```jsx function withLogger(WrappedComponent) { function LoggerComponent(props) { console.log(`[${WrappedComponent.name}] props:`, props); // logs on each render return <WrappedComponent {...props} />; } LoggerComponent.displayName = `withLogger(${WrappedComponent.name})`; return LoggerComponent; } const Hello = ({ name }) => <h1>Hello, {name}</h1>; const HelloWithLogger = withLogger(Hello); // <HelloWithLogger name="Alice" /> // Console: [Hello] props: { name: 'Alice' } // Output: <h1>Hello, Alice</h1> ``` No state, no side effects. The HOC wraps and logs. Notice `displayName` is set so DevTools shows `withLogger(Hello)`, not `Anonymous`. ### Intermediate: auth protection HOC ```jsx import { useContext } from 'react'; import { Navigate } from 'react-router-dom'; const withAuth = (WrappedComponent) => { function Authenticated(props) { const { user } = useContext(AuthContext); // hooks inside HOC work fine (React 16.8+) if (!user) return <Navigate to="/login" />; // redirect if not logged in return <WrappedComponent {...props} user={user} />; // inject user prop } Authenticated.displayName = `withAuth(${WrappedComponent.displayName || WrappedComponent.name})`; return Authenticated; }; const Dashboard = ({ user }) => <div>Welcome, {user.name}!</div>; const ProtectedDashboard = withAuth(Dashboard); // <ProtectedDashboard /> with no user -> redirects to /login // <ProtectedDashboard /> with user -> "Welcome, Alice!" ``` This pattern matches what `react-redux-auth-wrapper` does internally. `Dashboard` itself knows nothing about auth logic. The HOC owns that decision entirely. ### Advanced: data fetching HOC with cleanup ```jsx import { useState, useEffect } from 'react'; function withFetch(WrappedComponent, url) { function ComponentWithData(props) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const controller = new AbortController(); // cancel fetch on unmount fetch(url, { signal: controller.signal }) .then((res) => { if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); }) .then(setData) .catch((err) => { if (err.name !== 'AbortError') setError(err.message); }) .finally(() => setLoading(false)); return () => controller.abort(); // cleanup on unmount }, []); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return <WrappedComponent {...props} data={data} />; } ComponentWithData.displayName = `withFetch(${WrappedComponent.displayName || WrappedComponent.name})`; return ComponentWithData; } const UserList = ({ data }) => ( <ul>{data?.map(u => <li key={u.id}>{u.name}</li>)}</ul> ); const UserListWithData = withFetch(UserList, '/api/users'); // <UserListWithData /> -> fetches /api/users, shows loading state, then the list ``` The `AbortController` is what separates a production HOC from a demo. I've seen this omission cause memory warnings in dashboard apps with frequent route changes. Without it, a fetch completing after unmount tries to call `setData` on a dead component. You get a warning and a leak.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.