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?
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
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.
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?
| Situation | Empfehlung |
|---|---|
| 1-2 Ebenen Drilling | Props — explizit ist okay |
| 3-4 Ebenen Drilling, Wert wird oft gebraucht | Context |
| Tiefe Hierarchie, aber nur Layout-Composition | children-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 Selectors | Zustand, 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
useContextaufruft, 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:
// 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
- Passing Data Deeply with Context – react.dev
- Before you use Context – react.dev
- Composition vs. Inheritance – Legacy React Docs