Skip to solution
mediumFrontend

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

387 views
01

Understand 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.

hocreusabilitycompositiondesign patterns
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for React interviews — assumes components and props. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. The losses documented in section 4 were produced by actually building a naiv

Solution ready — 2 min read

Classified // press E to declassify

04

Explore the playground snippets

A careless HOC beside a correct one, showing exactly what is lost
Run Playground
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>
  );
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 60 of 119 decoded in the React.js track. One more won't hurt.

Back to track