Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Custom Hooks und Rules of Hooks in React

Custom Hooks und Rules of Hooks

~12 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026

Zum Abschluss von Phase 2: die zwei festen Regeln, die für ALLE Hooks gelten, und wie Sie eigene, wiederverwendbare Hooks bauen, indem Sie bestehende Hooks kombinieren.

Die zwei Rules of Hooks

  1. Hooks nur auf oberster Ebene aufrufen. Niemals innerhalb von if-Blöcken, Schleifen oder verschachtelten Funktionen – React verlässt sich auf die exakt gleiche REIHENFOLGE der Hook-Aufrufe bei jedem Rendern, um zu wissen, welcher State zu welchem useState-Aufruf gehört.
  2. Hooks nur aus React-Funktionskomponenten oder anderen Custom Hooks aufrufen. Niemals aus normalen JavaScript-Funktionen oder Klassen-Komponenten.
// FALSCH – Hook in einer Bedingung:
function Beispiel({ zeigeZaehler }) {
  if (zeigeZaehler) {
    const [anzahl, setAnzahl] = useState(0); // ✗ verboten
  }
  // ...
}

// RICHTIG – Hook immer aufrufen, Bedingung erst im JSX:
function Beispiel({ zeigeZaehler }) {
  const [anzahl, setAnzahl] = useState(0); // ✓ immer auf oberster Ebene

  return zeigeZaehler ? <p>{anzahl}</p> : null;
}

Achtung: Verstößt Ihr Code gegen diese Regeln, meldet die "react-hooks/rules-of-hooks"-ESLint-Regel (standardmäßig in jedem Vite-React-Projekt aktiv) das SOFORT beim Speichern im Editor – Sie müssen sich diese Regeln also nicht auswendig merken, nur verstehen, WARUM sie existieren.

Was ist ein Custom Hook?

Ein "Custom Hook" ist einfach eine ganz normale JavaScript-Funktion, die (a) mit use beginnt (reine Namenskonvention, aber wichtig – so erkennt React UND die ESLint-Regel, dass diese Funktion selbst wiederum andere Hooks aufrufen darf) und (b) einen oder mehrere eingebaute Hooks im Inneren verwendet. Wiederkehrende Hook-Kombinationen lassen sich so sauber wiederverwenden, statt sie in jeder Komponente zu duplizieren.

hooks/useDocumentTitle.js: unseren ersten Custom Hook bauen

Erinnern Sie sich an den document.title-Effekt aus Kapitel 8? Den lagern wir jetzt in einen wiederverwendbaren Hook aus. Legen Sie src/hooks/useDocumentTitle.js an:

src/hooks/useDocumentTitle.js
import { useEffect } from 'react';

export function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

Beachten Sie: [title] statt eines leeren Arrays – so aktualisiert sich der Tab-Titel automatisch neu, sobald sich title ändert, nicht nur einmal beim ersten Rendern. Das ist ein Verbesserung gegenüber dem Original aus Kapitel 8.

AuthContext.jsx erweitern: ein useAuth-Hook

Auch useContext(AuthContext) aus Kapitel 11 lässt sich in einen eigenen Hook verpacken – üblich in echten React-Projekten, weil es (a) den Import von AuthContext selbst aus den Komponenten verbirgt und (b) einen hilfreichen Fehler wirft, falls jemand useAuth() versehentlich AUSSERHALB von AuthProvider aufruft. Erweitern Sie src/context/AuthContext.jsx:

src/context/AuthContext.jsx
import { createContext, useContext, useState } from 'react';

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  function login(username) {
    setUser({ username });
  }

  function logout() {
    setUser(null);
  }

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (context === null) {
    throw new Error('useAuth() muss innerhalb von <AuthProvider> verwendet werden.');
  }
  return context;
}

AuthContext selbst wird jetzt NICHT mehr exportiert (kein export mehr vor const AuthContext) – von außen wird nur noch AuthProvider und useAuth verwendet, AuthContext ist reines Implementierungsdetail dieser Datei geworden.

App.jsx aktualisieren: beide Custom Hooks nutzen

src/App.jsx
import { useMemo, useRef, useState } from 'react';
import ProductCard from './components/ProductCard';
import { useAuth } from './context/AuthContext';
import { useDocumentTitle } from './hooks/useDocumentTitle';

const SAMPLE_PRODUCTS = [
  { sku: 'schuhe-01', name: 'Wanderschuhe', price: 89.99, imageUrl: 'https://picsum.photos/seed/1/300' },
  { sku: 'rucksack-01', name: 'Trekking-Rucksack', price: 59.5, imageUrl: 'https://picsum.photos/seed/2/300' },
  { sku: 'jacke-01', name: 'Regenjacke', price: 74.0, imageUrl: 'https://picsum.photos/seed/3/300' },
];

function App() {
  const { user, login, logout } = useAuth();
  const [query, setQuery] = useState('');
  const searchInputRef = useRef(null);

  useDocumentTitle(`Produktkatalog (${SAMPLE_PRODUCTS.length} Produkte)`);

  const gefiltert = SAMPLE_PRODUCTS.filter((produkt) =>
    produkt.name.toLowerCase().includes(query.toLowerCase())
  );

  const teuerstesProdukt = useMemo(() => {
    return gefiltert.reduce(
      (max, p) => (p.price > (max?.price ?? 0) ? p : max),
      null
    );
  }, [gefiltert]);

  return (
    <div className="app">
      <header className="app-header">
        <h1>Produktkatalog</h1>
        {user ? (
          <p>Angemeldet als {user.username} <button onClick={logout}>Abmelden</button></p>
        ) : (
          <button onClick={() => login('Max Mustermann')}>Anmelden</button>
        )}
      </header>
      <input
        ref={searchInputRef}
        type="text"
        placeholder="Produkt suchen..."
        value={query}
        onChange={(event) => setQuery(event.target.value)}
      />
      {teuerstesProdukt && (
        <p>Teuerstes Produkt: {teuerstesProdukt.name} ({teuerstesProdukt.price.toFixed(2)} €)</p>
      )}
      <div className="product-grid">
        {gefiltert.map((produkt) => (
          <ProductCard
            key={produkt.sku}
            name={produkt.name}
            price={produkt.price}
            imageUrl={produkt.imageUrl}
            onSelect={() => alert(produkt.name + ' angeklickt')}
          />
        ))}
      </div>
    </div>
  );
}

export default App;

Beachten Sie den fehlenden useEffect/useContext-Import in App.jsx jetzt – beide stecken versteckt in unseren eigenen Hooks. App.jsx selbst liest sich dadurch deutlich klarer: "Ich nutze Auth, ich setze einen Dokumenttitel", ohne die Details zu kennen. Damit ist Phase 2 (Hooks) abgeschlossen.

Projektstruktur nach diesem Kapitel

produktkatalog-web/
├── index.html
├── package.json
├── vite.config.js
└── src/
    ├── main.jsx
    ├── App.jsx                      (nutzt jetzt useAuth + useDocumentTitle)
    ├── index.css
    ├── context/
    │   └── AuthContext.jsx           (erweitert: useAuth-Hook)
    ├── hooks/
    │   └── useDocumentTitle.js       ← NEU
    └── components/
        └── ProductCard.jsx

Tipp: Eine gute Faustregel, WANN sich ein Custom Hook lohnt: Sobald Sie denselben Hook-Code (oder dieselbe Hook-Kombination) in einer zweiten Komponente kopieren würden, ist das ein starkes Signal, ihn stattdessen in einen Custom Hook auszulagern.