# Von HOCs und Render Props zu Custom Hooks

URL: https://www.mikebild.dev/de/blog/von-hocs-und-render-props-zu-custom-hooks/

Vier Ebenen im React-DevTools-Baum, bevor die erste erscheint, die tatsächlich etwas Sichtbares rendert: `withRouter`, darunter `withLoading`, darunter `withData`, und ganz unten die eigentliche Seite. Drei dieser Ebenen tragen nichts zur Darstellung bei. Sie existieren nur, um Zustand und Verhalten von außen nach innen zu reichen.

Dieser Baum ist kein Unfall, sondern die logische Folge einer Entscheidung, die React über Jahre mitgeschleppt hat. Denn stateful Logik an mehreren Stellen wiederzuverwenden, ohne sie zu duplizieren, hat das Framework in drei Generationen jeweils anders gelöst – und die ersten beiden Lösungen bezahlen dafür mit genau solchen Ebenen. Wer die Reihenfolge kennt, versteht auch, warum die älteren Muster trotzdem nicht wertlos sind.

## Die immer gleiche Frage

Ein Beispiel, an dem sich das gut zeigen lässt: Eine Komponente soll wissen, ob der Browser gerade online ist. Das braucht State, das braucht einen Effekt mit Event-Listenern, und das braucht Cleanup. Wenn drei Komponenten das brauchen, will niemand denselben Block dreimal schreiben.

Die naive Lösung ist Copy-Paste. Sie funktioniert genau so lange, bis sich die Logik ändert – dann ändert man sie an einer Stelle und vergisst die anderen beiden. Ab da suchen React-Entwickler nach einem Mechanismus, der die Logik an genau einem Ort hält.

Historisch sah der Weg dorthin so aus:

```mermaid
graph LR
  A[Copy-Paste] --> B[Mixins<br/>deprecated]
  B --> C[HOCs &<br/>Render Props]
  C --> D[Custom Hooks<br/>React 16.8 / 2019]
```

Mixins waren die erste Antwort in den ganz frühen Tagen mit `createClass`, wurden aber schnell als problematisch verworfen – unklare Herkunft von Properties, Namenskollisionen, implizite Abhängigkeiten. Danach kamen zwei Muster, die lange dominierten: Higher-Order Components und Render Props. Und seit React 16.8 – stabil seit Februar 2019 – gibt es Custom Hooks. Schauen wir uns die drei relevanten Antworten der Reihe nach an.

## Antwort 1: Higher-Order Components

Ein Higher-Order Component ist im Grunde ein alter Bekannter aus der funktionalen Programmierung. So wie eine Higher-Order Function eine Funktion nimmt und eine neue Funktion zurückgibt, nimmt ein HOC eine Komponente und gibt eine neue, erweiterte Komponente zurück. Die Signatur ist:

```js
// a function that takes a component and returns a new component
const withSomething = (Component) => EnhancedComponent;
```

Die Namenskonvention ist `withXxx`. Nehmen wir das Klassikerbeispiel `withLoading`, das einen Ladezustand kapselt:

```jsx
import { Fragment } from "react";

function withLoading(Wrapped) {
  return function WithLoading(props) {
    if (props.loading) {
      return <Spinner />;
    }
    return <Wrapped {...props} />;
  };
}
```

Angewendet wird das Ganze auf Modulebene, nicht im Render:

```jsx
const UserPageWithLoading = withLoading(UserPage);
```

Das liest sich sauber. Und es komponiert: Man kann mehrere HOCs stapeln, `withRouter(withLoading(withData(Page)))`. Genau da hat das Ganze aber seinen Preis.

Dass die Anwendung außerhalb des Renders passieren muss, wirkt wie eine Formalie – bis man einmal dagegen verstößt. Erzeugt man ein HOC direkt im `render()`, entsteht bei jedem Durchlauf eine neue Komponenten-Identität. React sieht einen anderen Typ, hängt den kompletten Subtree aus dem Baum und baut ihn neu auf – der gesamte State darunter geht verloren. Das ist einer der Bugs, die man einmal live debuggt und danach nie wieder produziert.

Dazu kommen zwei weitere Stolpersteine, die man kennen muss:

- Statische Methoden der Original-Komponente verschwinden, weil der Wrapper sie nicht automatisch mitkopiert – man braucht etwas wie `hoist-non-react-statics`.
- Refs gehen nicht als normale Prop durch den Wrapper hindurch. Wer eine Ref auf die innere Komponente braucht, muss `React.forwardRef` bemühen (verfügbar seit React 16.3).

Und die Grundregel, die alles zusammenhält: Komposition statt Mutation. Ein HOC mutiert die übergebene Komponente nicht, es umschließt sie in einem Container. Sobald jemand anfängt, die Original-Komponente direkt zu verändern, ist das Muster kaputt.

## Antwort 2: Render Props

Das zweite Muster dreht den Spieß um. Statt eine Komponente in eine andere zu wickeln, gibt man einer Komponente eine Funktion mit, die ihr sagt, was sie rendern soll. Das ist eine Render Prop: „a function prop that a component uses to know what to render“. Oft nutzt man dafür direkt `children` als Funktion.

```jsx
class OnlineStatus extends React.Component {
  state = { isOnline: false };

  handleOnline = () => this.setState({ isOnline: true });
  handleOffline = () => this.setState({ isOnline: false });

  componentDidMount() {
    window.addEventListener("online", this.handleOnline);
    window.addEventListener("offline", this.handleOffline);
  }

  componentWillUnmount() {
    window.removeEventListener("online", this.handleOnline);
    window.removeEventListener("offline", this.handleOffline);
  }

  render() {
    return this.props.children(this.state.isOnline);
  }
}
```

Aufgerufen wird das dann so:

```jsx
<OnlineStatus>
  {(isOnline) => (
    <span>{isOnline ? "Online" : "Offline"}</span>
  )}
</OnlineStatus>
```

Das ist flexibler als ein HOC, weil der Aufrufer die volle Kontrolle über das Rendering behält. Aber auch hier gibt es einen Preis: Wenn man die Funktion inline im JSX erzeugt, wird sie bei jedem Render neu erstellt. Für eine `PureComponent` oder eine mit `memo` optimierte Kind-Komponente ist das Gift – der Shallow-Compare schlägt fehl, weil die Funktionsreferenz jedes Mal neu ist, und die Optimierung läuft ins Leere. Man müsste die Funktion als stabile Instanzmethode auslagern, was die vermeintliche Eleganz wieder auffrisst.

## Das gemeinsame Problem: Wrapper Hell

Beide Muster – HOC und Render Props – teilen Logik über dieselbe Mechanik: über zusätzliche Ebenen im Komponentenbaum. Jedes HOC ist ein Wrapper, jede Render Prop eine weitere Verschachtelung. Bei einer, zwei Ebenen merkt man nichts. Bei fünf sieht der React-DevTools-Baum aus wie eine Zwiebel, und das nennt man liebevoll „wrapper hell“.

Genau dieser Baum ist der entscheidende Punkt. Schauen wir ihn uns an:

```mermaid
graph TD
  subgraph HOC["HOC / Render Props: verschachtelt"]
    R1[withRouter] --> L1[withLoading]
    L1 --> D1[withData]
    D1 --> P1[Page]
  end
```

Jede Zwischenebene existiert nur, um Logik hereinzureichen. Sie hat keinen eigenen fachlichen Sinn, sie ist reine Infrastruktur im Baum. Und weil die Daten von außen nach innen durchgereicht werden, entstehen leicht Zwischenschichten mit veralteten Closures – ein weiterer Debugging-Klassiker.

## Antwort 3: Custom Hooks

Custom Hooks lösen das Problem an einer anderen Stelle. Ein Custom Hook ist einfach eine normale JavaScript-Funktion, deren Name mit `use` beginnt und die andere Hooks aufrufen darf. Kein Wrapper, keine zusätzliche Komponente, keine neue Ebene im Baum. Die Logik zieht in eine Funktion um, die Komponente ruft sie auf – fertig.

Dasselbe `withLoading`-Problem sieht als Hook so aus, dass der Hook den Zustand liefert und die Komponente selbst entscheidet, was sie damit rendert:

```jsx
import { useState, useEffect } from "react";

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, []);

  return isOnline;
}
```

Und die Nutzung ist so unspektakulär, wie sie sein sollte:

```jsx
function StatusBar() {
  const isOnline = useOnlineStatus();
  return <span>{isOnline ? "Online" : "Offline"}</span>;
}
```

Vergleicht man das mit der `OnlineStatus`-Render-Prop von oben: dieselbe Logik, aber ohne die Verschachtelung, ohne die inline-Funktion, ohne die Klasse. Der Baum bleibt flach:

```mermaid
graph TD
  Page2[Page] -.ruft auf.-> H1[useData]
  Page2 -.ruft auf.-> H2[useLoading]
  Page2 -.ruft auf.-> H3[useRouter]
```

Die Hooks hängen seitlich an der Komponente – es sind Funktionsaufrufe, keine Baumebenen. Das ist der ganze Trick, und es ist ein großer.

Zwei Eigenschaften von Custom Hooks sind es wert, explizit ausgesprochen zu werden, weil sie oft missverstanden werden:

- Custom Hooks teilen **Logik, nicht State**. Rufen zwei Komponenten `useOnlineStatus()` auf, hat jede ihren eigenen, unabhängigen State. Der Hook ist ein Rezept, kein geteilter Topf.
- Custom Hooks re-rendern mit ihrer Komponente. Bei jedem Re-Render bekommt der Hook frische Props und aktuellen State – die „veralteten Closures“, die in HOC-Zwischenschichten so leicht entstehen, sind hier deutlich seltener ein Thema.

## Regeln, die auch für Custom Hooks gelten

Das `use`-Präfix ist keine Kosmetik. Es signalisiert dem Linter und den Lesern, dass diese Funktion selbst Hooks aufrufen darf – und dass für sie die Rules of Hooks gelten. Konkret heißt das:

Nur React-Komponenten und andere Hooks dürfen Hooks aufrufen. Eine gewöhnliche Funktion ohne `use` darf kein `useState` enthalten. Umgekehrt gilt genauso: Eine Funktion, die gar keine Hooks aufruft, sollte nicht `use` heißen. Denn sonst nimmt man sich die Freiheit, sie bedingt oder in einer Schleife aufzurufen – was bei echten Hooks verboten ist. Das Präfix ist also ein Vertrag in beide Richtungen. Durchgesetzt wird das Ganze vom `eslint-plugin-react-hooks`, das ich in keinem React-Projekt weglasse.

Eine Falle, die weniger technisch als konzeptionell ist, sehe ich in Codebasen immer wieder: der Drang, gleich generische „Lifecycle“-Hooks zu bauen, etwa ein `useMount`, das einen Effekt beim Mounten kapselt. Das ist meist ein schlechter Tausch. Ein Custom Hook sollte einen konkreten, benennbaren Anwendungsfall abbilden – `useOnlineStatus`, `useChatRoom`, `useFormInput`. Wenn der Name des Hooks bereits erklärt, was er fachlich tut, ist er gut geschnitten. Wenn er nur einen React-Mechanismus umbenennt, hat man nichts gewonnen und die Abstraktion später im Weg.

## Wann HOCs heute noch sinnvoll sind

Nach all dem klingt es, als hätten Custom Hooks die beiden älteren Muster restlos ersetzt. Für das Teilen von stateful Logik stimmt das auch weitgehend. Aber es gibt eine Handvoll Aufgaben, für die HOCs weiterhin die passende Form sind – nämlich echte Cross-Cutting-Concerns, die von außen an eine Komponente herangetragen werden.

Das klassische Beispiel ist Context-Injection: ein HOC, das einer beliebigen Komponente einen Wert aus dem Context als Prop verfügbar macht, ohne dass die Komponente selbst den Context kennen muss. Solche `withXxx`-Wrapper begegnen einem in älteren Bibliotheken oft, und sie erfüllen dort einen Zweck, den ein Hook nicht sauber abdeckt – etwa wenn Klassenkomponenten im Spiel sind, die gar keine Hooks aufrufen können. Wer eine bestehende Codebasis mit Klassenkomponenten pflegt, wird HOCs also noch länger begegnen.

Was ich hingegen nicht mehr empfehle, ist der Griff zu HOC-Baukästen wie `recompose`. Die Bibliothek war zu ihrer Zeit ein cleveres Werkzeug, um Funktionen wie `withState` oder `withReducer` zu komponieren:

```js
// recompose - historical, deprecated since 2018
import { compose, withState } from "recompose";

const enhance = compose(
  withState("count", "setCount", 0)
);
```

Der Autor selbst hat `recompose` allerdings schon 2018 als deprecated markiert und dabei ausdrücklich auf Hooks als Nachfolger verwiesen. Man begegnet dem Paket also nur noch als historischem Kontext in älteren Repositories – als aktuelle Empfehlung taugt es nicht.

## Der rote Faden

Drei Antworten auf eine Frage, und die Reihenfolge erzählt eine klare Geschichte. HOCs und Render Props haben gezeigt, dass sich Logik teilen lässt, ohne sie zu kopieren – aber sie haben es über den Komponentenbaum gelöst und dafür mit Verschachtelung, verlorenen Refs und statischen Methoden, aufgehobenen `memo`-Optimierungen und „wrapper hell“ bezahlt. Custom Hooks lösen dieselbe Aufgabe über einen simplen Funktionsaufruf und lassen den Baum flach.

Für geteilte Logik ist der erste Griff deshalb heute ein Custom Hook mit `use`-Präfix, der einen konkreten Use-Case kapselt. HOCs bleiben im Werkzeugkasten für die Cross-Cutting-Fälle und für die Arbeit an bestehendem Code mit Klassenkomponenten. Und Render Props tauchen noch dort auf, wo eine Komponente wirklich Kontrolle über das Rendering an den Aufrufer zurückgeben will. Aber der Standardfall – dieselbe Logik in mehreren Komponenten – hat seit 2019 eine gute, unspektakuläre Antwort. Man muss nur wissen, warum die anderen beiden vorher da waren.

## Weiterführende Quellen

- [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) – das aktuelle Kapitel zu Custom Hooks
- [Higher-Order Components](https://legacy.reactjs.org/docs/higher-order-components.html) – die historische HOC-Dokumentation
- [Render Props](https://legacy.reactjs.org/docs/render-props.html) – das Render-Props-Muster im Original
- [Hooks – Custom Hooks (Legacy)](https://legacy.reactjs.org/docs/hooks-custom.html) – das historische Custom-Hooks-Kapitel
- [Rules of Hooks / Invalid Hook Call Warning](https://react.dev/warnings/invalid-hook-call-warning) – warum das `use`-Präfix und die Regeln zählen
