Props Drilling bezeichnet das Durchreichen eines Werts über mehrere Komponenten-Ebenen, von denen die meisten den Wert gar nicht selbst nutzen — sie reichen ihn nur weiter. Ab drei oder vier Ebenen wird das schmerzhaft: Refactorings sind teuer, Komponenten haben Props, die sie nicht angehen. Context löst das, indem der Wert in beliebiger Tiefe direkt abrufbar ist. Aber: Context bringt eigene Probleme mit (Re-Render-Performance, weniger Explizitheit). Und manchmal ist die beste Lösung weder Drilling noch Context, sondern Component-Composition — Kinder als children durchreichen, sodass die Daten nicht durch alle Ebenen müssen.

Was ist Props Drilling?

TypeScript PropsDrilling.jsx
function App() {
    const user = { name: 'Anna' };
    return <Layout user={user} />;
}

function Layout({ user }) {
    // Layout nutzt user NICHT, reicht ihn nur weiter
    return <Sidebar user={user} />;
}

function Sidebar({ user }) {
    // Sidebar nutzt user NICHT, reicht ihn nur weiter
    return <UserPanel user={user} />;
}

function UserPanel({ user }) {
    // Erst hier wird user genutzt
    return <p>Hallo, {user.name}</p>;
}

Layout und Sidebar haben eine user-Prop, die sie nicht verwenden. Drei Ebenen Boilerplate für einen einzigen Wert. Bei Refactor (neue Zwischen-Komponente, Reihenfolgen-Wechsel, TypeScript-Typen) entstehen viele Berührungs-Punkte.

Lösung 1: Context

TypeScript MitContext.jsx
const UserContext = createContext(null);

function App() {
    const user = { name: 'Anna' };
    return (
        <UserContext.Provider value={user}>
            <Layout />
        </UserContext.Provider>
    );
}

function Layout() { return <Sidebar />; }
function Sidebar() { return <UserPanel />; }

function UserPanel() {
    const user = useContext(UserContext);
    return <p>Hallo, {user.name}</p>;
}

Layout und Sidebar brauchen die user-Prop nicht mehr. Konsumenten ziehen den Wert direkt aus dem Context.

Lösung 2: Component-Composition

Oft ist die beste Lösung, gar nicht erst durch Zwischen-Komponenten zu drillen — sondern die Konsumenten als children direkt von oben hineinzukomponieren.

TypeScript Composition.jsx
function App() {
    const user = { name: 'Anna' };
    return (
        <Layout sidebar={<UserPanel user={user} />}>
            {/* Main-Inhalt */}
        </Layout>
    );
}

function Layout({ sidebar, children }) {
    return (
        <div>
            <aside>{sidebar}</aside>
            <main>{children}</main>
        </div>
    );
}

function UserPanel({ user }) {
    return <p>Hallo, {user.name}</p>;
}

Layout und der Sidebar-Slot sind generisch. UserPanel ist bereits mit Daten gefüllt, bevor es in den Layout-Slot wandert. Kein Drilling, kein Context — saubere Komposition.

Wann was?

SituationEmpfehlung
1-2 Ebenen DrillingProps — explizit ist okay
3-4 Ebenen Drilling, Wert wird oft gebrauchtContext
Tiefe Hierarchie, aber nur Layout-Compositionchildren-Composition
Globale Werte (Theme, Locale, Auth-User)Context
Hochfrequente Updates (Cursor, Animation)NICHT Context — externer Store
Server-State (API-Daten)TanStack Query, SWR
Komplexer globaler State mit SelectorsZustand, Jotai, Redux

Faustregel: Erst Composition versuchen. Wenn das nicht passt: Context für stabile Werte. Für State-Management: spezifische Libraries.

Trade-offs von Context

Vorteile:

  • Kein Drilling.
  • Direkter Zugriff in beliebiger Tiefe.
  • Composable mit anderen Contexts.

Nachteile:

  • Re-Render-Kosten: bei value-Wechsel rendern alle Konsumenten, auch wenn sie den geänderten Teil gar nicht nutzen.
  • Implizite Abhängigkeit: Komponente, die useContext aufruft, verlangt einen Provider — wird beim Verschieben übersehen.
  • Testen wird umständlicher: Tests müssen den Provider drumrum wrappen.
  • Tooling-Sichtbarkeit: Props sind in DevTools explizit sichtbar, Context-Werte müssen man explizit aufklappen.

Trade-offs von Props Drilling

Vorteile:

  • Explizit — beim Lesen sieht man genau, woher der Wert kommt.
  • Refactoring-freundlich für Tools (ESLint, TypeScript erkennen falsche Pfade).
  • Keine implizite globale Abhängigkeit.

Nachteile:

  • Viel Boilerplate bei vielen Ebenen.
  • Refactor-Schmerz: neue Zwischen-Komponente = überall Prop ergänzen.
  • Komponenten haben Props, die sie nicht selbst nutzen — Code-Smell.

Beispiel — kombinierte Strategie

In einer realen App nutzen erfahrene Teams beide Werkzeuge bewusst:

TypeScript Kombiniert.jsx
// Globaler User (selten gewechselt) — Context
<AuthProvider>
    {/* Theme (selten gewechselt) — Context */}
    <ThemeProvider>
        {/* App-Layout mit Composition für Slots */}
        <Layout
            sidebar={<Sidebar />}
            header={<Header />}
        >
            {/* Form-Daten als Props (lokal) */}
            <UserForm
                initialValues={initialValues}
                onSubmit={handleSubmit}
            />
        </Layout>
    </ThemeProvider>
</AuthProvider>

Auth + Theme → Context (App-weit, selten gewechselt). Layout-Slots → Composition. Form-Daten → Props (lokal, explizit).

Interessantes

Drei oder vier Ebenen Drilling sind Schmerzschwelle.

Eine oder zwei Ebenen sind okay — explizit und nachvollziehbar. Ab drei wird's lästig, ab vier ist Refactoring fällig. Faustregel: wenn drei Komponenten hintereinander dieselbe Prop durchreichen ohne sie zu nutzen, ist Context oder Composition die Antwort.

Composition ist die unterschätzte Lösung.

React-Doku: „Before you use context, try composition." Viele Drilling-Probleme verschwinden, wenn man die tief verschachtelten Komponenten als children oder Slot-Props von oben hineinkomponiert.

Context macht Komponenten an Provider gebunden.

Eine Komponente, die useContext(UserContext) aufruft, funktioniert NUR innerhalb eines UserProvider. Beim Wiederverwenden außerhalb des bekannten App-Kontexts: Provider muss mit.

Test-Setup mit Context: Provider als Wrapper.

render(<UserContext.Provider value={mockUser}><Component /></UserContext.Provider>). Wer viele Tests schreibt: einen renderWithProviders-Helper bauen, der alle nötigen Provider wrappt.

Props-Drilling-Tooling: TypeScript fängt fehlende Props ab.

In TS muss jede Zwischen-Komponente die Prop typisieren. Beim Refactor sieht der Compiler sofort, wo etwas fehlt. Bei Context: kein Compiler-Hint, sondern Runtime-Null-Check.

Context ersetzt NICHT Redux/Zustand.

Context teilt Werte zwischen Komponenten — er hat KEIN Selector-System für „nur dieser Teil änderte sich, rendere nur diese Konsumenten". Redux/Zustand machen das. Bei komplexem State mit vielen partiellen Konsumenten ist Context Performance-Killer.

Server-State gehört NICHT in Context.

API-Daten, Cache, Loading-States, Optimistic-Updates → TanStack Query, SWR. Diese Libraries lösen Server-State spezifisch (Stale-While-Revalidate, Mutations, Pagination). Context wäre nur eine umständliche Hand-Rolle.

Composition + Context = die produktivste Kombination.

Composition für Layout/Strukturen, Context für globale stabile Werte (Auth, Theme, Locale), Props für lokale Daten. Drei Werkzeuge mit klaren Rollen — selten alle ersetzbar durch eines.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Context

Zur Übersicht