useContext
Sobald eine App in mehrere Komponenten aufgeteilt wird, entsteht ein typisches Problem: Daten müssen durch Komponenten hindurchgereicht werden, die sie selbst gar nicht brauchen. useContext bietet einen direkten Kanal — ein Wert wird einmal bereitgestellt und ist für alle Komponenten im Baum darunter direkt zugänglich.
Das Problem: Prop Drilling
Abschnitt betitelt „Das Problem: Prop Drilling“Die Todo-App wächst. Wir teilen sie in Komponenten auf — und plötzlich wandert dispatch durch die gesamte Hierarchie:
function TodoApp() { const [todos, dispatch] = useReducer(todoReducer, [])
return ( <> <TodoListe todos={todos} dispatch={dispatch} /> <TodoFooter todos={todos} dispatch={dispatch} /> </> )}
function TodoListe({ todos, dispatch }) { return ( <ul> {todos.map(todo => ( <TodoEintrag key={todo.id} todo={todo} dispatch={dispatch} /> ))} </ul> )}
function TodoEintrag({ todo, dispatch }) { // dispatch wird hier gebraucht — aber TodoListe hat es nur durchgereicht}TodoListe transportiert dispatch ohne es selbst zu verwenden. Bei tieferen Bäumen oder mehr Werten wächst das Problem schnell.
Context erstellen
Abschnitt betitelt „Context erstellen“Ein Context ist ein benannter Kanal für einen Wert. Du erstellst ihn einmal mit createContext:
import { createContext } from 'react'
type TodoFilterContextType = { filter: 'alle' | 'offen' | 'erledigt' setFilter: (f: 'alle' | 'offen' | 'erledigt') => void}
const TodoFilterContext = createContext<TodoFilterContextType | null>(null)null als Default-Wert signalisiert: dieser Context ist nur innerhalb eines Providers sinnvoll. Den Typ als | null zu definieren erzwingt eine explizite Prüfung beim Konsumieren.
Wert bereitstellen: Provider
Abschnitt betitelt „Wert bereitstellen: Provider“Der Provider umschließt den Teil des Baums, der Zugriff auf den Context bekommen soll:
import { useState } from 'react'
function TodoApp() { const [filter, setFilter] = useState<'alle' | 'offen' | 'erledigt'>('alle')
return ( <TodoFilterContext.Provider value={{ filter, setFilter }}> <TodoFilter /> <TodoListe /> </TodoFilterContext.Provider> )}Alles innerhalb des Providers kann den filter-Wert und setFilter direkt lesen — ohne Props.
Wert konsumieren: useContext
Abschnitt betitelt „Wert konsumieren: useContext“import { useContext } from 'react'
function TodoFilter() { const ctx = useContext(TodoFilterContext) if (!ctx) throw new Error('TodoFilter muss innerhalb von TodoApp verwendet werden')
return ( <nav> {(['alle', 'offen', 'erledigt'] as const).map(f => ( <button key={f} onClick={() => ctx.setFilter(f)} style={{ fontWeight: ctx.filter === f ? 'bold' : 'normal' }} > {f} </button> ))} </nav> )}
function TodoListe() { const ctx = useContext(TodoFilterContext) if (!ctx) throw new Error('TodoListe muss innerhalb von TodoApp verwendet werden')
// Der Filter kommt direkt aus dem Context — kein Prop nötig const { filter } = ctx // ...}Custom Hook: sauberere API
Abschnitt betitelt „Custom Hook: sauberere API“Das Muster useContext + Null-Prüfung wiederholt sich in jeder Komponente. Extrahiere es in einen eigenen Hook:
function useTodoFilter() { const ctx = useContext(TodoFilterContext) if (!ctx) throw new Error('useTodoFilter muss innerhalb von TodoApp verwendet werden') return ctx}Der Aufruf in Komponenten wird dadurch minimal:
function TodoFilter() { const { filter, setFilter } = useTodoFilter() // ...}
function TodoListe() { const { filter } = useTodoFilter() // ...}Der Custom Hook ist gleichzeitig die einzige Stelle, an der TodoFilterContext referenziert wird — alle anderen Komponenten kennen nur useTodoFilter.
TypeScript: Das createContext<T | null>(null)-Muster
Abschnitt betitelt „TypeScript: Das createContext<T | null>(null)-Muster“Eine verbreitete Alternative ist, einen Fake-Default-Wert zu übergeben:
// ❌ Unsicher — leeres Objekt befriedigt den Typ nur auf dem Papierconst TodoFilterContext = createContext<TodoFilterContextType>({} as TodoFilterContextType)Das kompiliert, aber wenn useContext außerhalb des Providers aufgerufen wird, ist der Wert {} — und der Code bricht zur Laufzeit mit verwirrenden Fehlern.
Das | null-Muster ist expliziter:
// ✅ Sicher — null zwingt zur Prüfung, Fehler ist früh und lesbarconst TodoFilterContext = createContext<TodoFilterContextType | null>(null)Der Custom Hook ist die einzige Stelle, die diese Prüfung macht — und er gibt eine klare Fehlermeldung, lange bevor ein undefined-Property-Zugriff crasht. Alle anderen Komponenten sehen davon nichts.
Wann Context, wann Props?
Abschnitt betitelt „Wann Context, wann Props?“Context ist kein Ersatz für Props — er löst ein spezifisches Problem:
| Props | Context | |
|---|---|---|
| Datenrichtung | explizit, direkt | implizit, durch den Baum |
| Geeignet für | direkte Eltern-Kind-Daten | weit verteilte Querschnittsdaten |
| Nachvollziehbarkeit | hoch — sichtbar in JSX | geringer — Herkunft nicht im JSX |
| Kopplung | gering | Komponente hängt am Context |
Faustregel: Props für alles, was eine direkte Verbindung hat. Context für Werte, die viele Komponenten auf verschiedenen Ebenen brauchen — Authentifizierung, Theme, Sprache, aktiver Filter, globaler App-State.
Ausblick
Abschnitt betitelt „Ausblick“Bisher haben wir Context mit einem einfachen useState-Wert kombiniert. Im nächsten Kapitel verbinden wir Context mit useReducer — und bauen damit ein vollständiges, selbstgemachtes State-Management für die gesamte Todo-App.