Skip to the solution
MediumFrontend7 min read

What are Higher-Order Components (HOCs) in React?

387 views

The problem

Question presented to candidate: "What is a Higher-Order Component, and would you write one today?"

What a strong answer should cover:

  • A HOC is a function that takes a component and returns a new component β€” a pattern, not an API. React ships nothing called HOC.
  • It was the pre-Hooks answer to sharing behaviour across components: withRouter, connect, withStyles.
  • The naming convention (withX) and the rule that a HOC must be pure β€” it must not mutate the component it receives.
  • The concrete problems: static methods are not copied, displayName is lost so DevTools shows Anonymous, refs do not pass through without forwarding, and prop-name collisions are silent.
  • "Wrapper hell" β€” deeply nested HOCs producing an unreadable component tree.
  • Typing them in TypeScript is genuinely awkward.
  • Custom Hooks replaced them for behaviour sharing: no wrapper component, no collisions, no lost statics, far better typing.
  • Where HOCs still legitimately appear: injecting props into a component you do not control, cross-cutting wrappers like error boundaries or analytics, and older library APIs.

Clarifying questions expected:

  • "Is this a legacy codebase, or new code?" β€” the answer changes completely.
  • "Are we sharing behaviour, or wrapping rendering?" β€” Hooks cover the first, HOCs still fit the second.

Code / implementation expected: Yes β€” a HOC with hoistNonReactStatics-style handling and a displayName, plus the Hook equivalent.

  • hoc
  • reusability
  • composition
  • design patterns

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

7 min read Β· 9 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactA careless HOC beside a correct one, showing exactly what is lost
import { useState } from "react";

// A component with a static method and a displayName β€” both easy to lose.
function Panel({ title, tone }) {
  return (
    <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 10, marginBottom: 8 }}>
      <strong>{title}</strong> <span style={{ color: "#666", fontSize: 13 }}>tone: {tone}</span>
    </div>
  );
}
Panel.displayName = "Panel";
Panel.describe = () => "I am a static method on Panel";

// ❌ CARELESS: returns an anonymous function and copies nothing across.
function withToneNaive(Wrapped) {
  return function (props) {
    return <Wrapped {...props} tone="naive" />;
  };
}

// βœ… CORRECT: names the wrapper for DevTools and hoists the statics.
// In a real project use the hoist-non-react-statics package, which knows
// which keys React itself owns and must not be copied.
const REACT_STATICS = new Set(["displayName", "name", "propTypes", "defaultProps", "$$typeof"]);
function withToneCorrect(Wrapped) {
  function WithTone(props) {
    return <Wrapped {...props} tone="correct" />;
  }
  const inner = Wrapped.displayName || Wrapped.name || "Component";
  WithTone.displayName = "withTone(" + inner + ")";
  for (const key of Object.getOwnPropertyNames(Wrapped)) {
    if (!REACT_STATICS.has(key) && typeof Wrapped[key] === "function") {
      WithTone[key] = Wrapped[key];
    }
  }
  return WithTone;
}

// Apply HOCs at MODULE level. Doing this inside a component body would create
// a new component type every render and remount the whole subtree.
const NaivePanel = withToneNaive(Panel);
const CorrectPanel = withToneCorrect(Panel);

export default function App() {
  const [report, setReport] = useState(null);

  const inspect = () =>
    setReport({
      originalStatic: typeof Panel.describe,
      naiveStatic: typeof NaivePanel.describe,
      correctStatic: typeof CorrectPanel.describe,
      naiveName: NaivePanel.displayName || "(none β€” shows as Anonymous)",
      correctName: CorrectPanel.displayName,
    });

  const td = { padding: "4px 12px 4px 0", fontFamily: "ui-monospace, monospace", fontSize: 13 };

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6 }}>
      <NaivePanel title="Wrapped by the careless HOC" />
      <CorrectPanel title="Wrapped by the correct HOC" />

      <button onClick={inspect}>Inspect what survived the wrapping</button>

      {report && (
        <table style={{ marginTop: 12, borderCollapse: "collapse" }}>
          <tbody>
            <tr><td style={td}>Panel.describe</td><td style={td}>{report.originalStatic}</td></tr>
            <tr><td style={td}>NaivePanel.describe</td><td style={{ ...td, color: "#a33" }}>{report.naiveStatic}</td></tr>
            <tr><td style={td}>CorrectPanel.describe</td><td style={{ ...td, color: "#161" }}>{report.correctStatic}</td></tr>
            <tr><td style={td}>NaivePanel.displayName</td><td style={{ ...td, color: "#a33" }}>{report.naiveName}</td></tr>
            <tr><td style={td}>CorrectPanel.displayName</td><td style={{ ...td, color: "#161" }}>{report.correctName}</td></tr>
          </tbody>
        </table>
      )}
    </div>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 60 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
Up nextMedium

Explain the purpose of `useEffect`.

Continue