React Context API
Die Context API ermöglicht es dir, Daten durch den Component Tree zu teilen, ohne Props manuell durch jede Ebene zu reichen (Props Drilling).
Das Props Drilling Problem
// ❌ Props durch viele Ebenen reichen
function App() {
const [user, setUser] = useState({ name: 'Anna', theme: 'dark' })
return <Parent user={user} />
}
function Parent({ user }) {
return <Child user={user} />
}
function Child({ user }) {
return <GrandChild user={user} />
}
function GrandChild({ user }) {
return <div>Hallo {user.name}!</div>
}
// User wird durch Parent & Child gereicht,
// obwohl sie es gar nicht brauchen! 😫Context API - Die Lösung
import { createContext, useContext, useState } from 'react'
// 1. Context erstellen
const UserContext = createContext()
// 2. Provider Component
function App() {
const [user, setUser] = useState({ name: 'Anna', theme: 'dark' })
return (
<UserContext.Provider value={{ user, setUser }}>
<Parent />
</UserContext.Provider>
)
}
// 3. Parent braucht keine Props mehr!
function Parent() {
return <Child />
}
function Child() {
return <GrandChild />
}
// 4. GrandChild holt sich Daten direkt aus Context
function GrandChild() {
const { user } = useContext(UserContext)
return <div>Hallo {user.name}!</div>
}
// ✅ Keine Props Drilling mehr! 🎉Context erstellen - Schritt für Schritt
1. Context erstellen
import { createContext } from 'react'
// Context mit Default-Wert
const ThemeContext = createContext('light')
// Oder ohne Default-Wert
const UserContext = createContext()
// Context mit mehreren Werten
const AppContext = createContext({
user: null,
theme: 'light',
language: 'de'
})2. Provider Component
function App() {
const [theme, setTheme] = useState('light')
// value: Daten die geteilt werden
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Layout>
<Header />
<Main />
<Footer />
</Layout>
</ThemeContext.Provider>
)
}
// Alle Child-Komponenten haben Zugriff auf theme!3. Context konsumieren
import { useContext } from 'react'
function Header() {
// Context-Daten holen
const { theme, setTheme } = useContext(ThemeContext)
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light')
}
return (
<header className={theme}>
<button onClick={toggleTheme}>
Theme: {theme}
</button>
</header>
)
}Theme Context - Vollständiges Beispiel
import { createContext, useContext, useState } from 'react'
// 1. Context erstellen
const ThemeContext = createContext()
// 2. Custom Hook für einfacheren Zugriff
export function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme muss innerhalb ThemeProvider verwendet werden')
}
return context
}
// 3. Provider Component
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light')
}
const value = {
theme,
toggleTheme
}
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
)
}
// 4. In App verwenden
function App() {
return (
<ThemeProvider>
<Layout />
</ThemeProvider>
)
}
// 5. Theme in beliebiger Komponente verwenden
function Header() {
const { theme, toggleTheme } = useTheme()
return (
<header style={{ background: theme === 'light' ? '#fff' : '#333' }}>
<button onClick={toggleTheme}>
Toggle Theme
</button>
</header>
)
}Auth Context - Praktisches Beispiel
import { createContext, useContext, useState } from 'react'
const AuthContext = createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(false)
const login = async (email, password) => {
setLoading(true)
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
})
const data = await response.json()
setUser(data.user)
return { success: true }
} catch (error) {
return { success: false, error: error.message }
} finally {
setLoading(false)
}
}
const logout = () => {
setUser(null)
}
const value = {
user,
login,
logout,
loading,
isAuthenticated: !!user
}
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
)
}
// Verwenden
function LoginPage() {
const { login, loading } = useAuth()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = async (e) => {
e.preventDefault()
const result = await login(email, password)
if (result.success) {
// Redirect to dashboard
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button disabled={loading}>
{loading ? 'Loading...' : 'Login'}
</button>
</form>
)
}
function Dashboard() {
const { user, logout } = useAuth()
return (
<div>
<h1>Welcome {user.name}!</h1>
<button onClick={logout}>Logout</button>
</div>
)
}Mehrere Contexts kombinieren
import { AuthProvider } from './contexts/AuthContext'
import { ThemeProvider } from './contexts/ThemeContext'
import { LanguageProvider } from './contexts/LanguageContext'
function App() {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
<Layout />
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
)
}
// Oder mit Custom AppProvider
export function AppProvider({ children }) {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
{children}
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
)
}
// Dann in App
function App() {
return (
<AppProvider>
<Layout />
</AppProvider>
)
}Shopping Cart Context
import { createContext, useContext, useState } from 'react'
const CartContext = createContext()
export function useCart() {
return useContext(CartContext)
}
export function CartProvider({ children }) {
const [items, setItems] = useState([])
const addItem = (product) => {
setItems(prev => {
const existing = prev.find(item => item.id === product.id)
if (existing) {
return prev.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
)
}
return [...prev, { ...product, quantity: 1 }]
})
}
const removeItem = (productId) => {
setItems(prev => prev.filter(item => item.id !== productId))
}
const updateQuantity = (productId, quantity) => {
setItems(prev =>
prev.map(item =>
item.id === productId ? { ...item, quantity } : item
)
)
}
const clearCart = () => {
setItems([])
}
const total = items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
)
const value = {
items,
addItem,
removeItem,
updateQuantity,
clearCart,
total,
itemCount: items.length
}
return (
<CartContext.Provider value={value}>
{children}
</CartContext.Provider>
)
}
// Product Component
function ProductCard({ product }) {
const { addItem } = useCart()
return (
<div>
<h3>{product.name}</h3>
<p>{product.price}€</p>
<button onClick={() => addItem(product)}>
In den Warenkorb
</button>
</div>
)
}
// Cart Component
function Cart() {
const { items, total, removeItem } = useCart()
return (
<div>
<h2>Warenkorb</h2>
{items.map(item => (
<div key={item.id}>
<span>{item.name} x {item.quantity}</span>
<span>{item.price * item.quantity}€</span>
<button onClick={() => removeItem(item.id)}>
Entfernen
</button>
</div>
))}
<h3>Total: {total}€</h3>
</div>
)
}Context mit useReducer
import { createContext, useContext, useReducer } from 'react'
const TodoContext = createContext()
// Reducer
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, {
id: Date.now(),
text: action.payload,
completed: false
}]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
)
case 'DELETE_TODO':
return state.filter(todo => todo.id !== action.payload)
default:
return state
}
}
export function TodoProvider({ children }) {
const [todos, dispatch] = useReducer(todoReducer, [])
const addTodo = (text) => {
dispatch({ type: 'ADD_TODO', payload: text })
}
const toggleTodo = (id) => {
dispatch({ type: 'TOGGLE_TODO', payload: id })
}
const deleteTodo = (id) => {
dispatch({ type: 'DELETE_TODO', payload: id })
}
return (
<TodoContext.Provider value={{ todos, addTodo, toggleTodo, deleteTodo }}>
{children}
</TodoContext.Provider>
)
}
export function useTodos() {
return useContext(TodoContext)
}📝 Quiz
Wann solltest du Context API verwenden?
Tipps & Tricks
Context Default Values
// Default-Wert wird nur verwendet wenn kein Provider vorhanden ist
const ThemeContext = createContext('light')
// Ohne Provider
function App() {
return <Child /> // theme = 'light'
}
// Mit Provider
function App() {
return (
<ThemeContext.Provider value="dark">
<Child /> // theme = 'dark'
</ThemeContext.Provider>
)
}
Custom Hook Pattern
// ✅ IMMER einen Custom Hook erstellen
export function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme muss innerhalb ThemeProvider verwendet werden')
}
return context
}
// Statt direkt useContext zu verwenden
const theme = useTheme() // ✅
const theme = useContext(ThemeContext) // ❌
Context Files organisieren
src/
├── contexts/
│ ├── AuthContext.jsx
│ ├── ThemeContext.jsx
│ └── CartContext.jsx
├── App.jsx
└── main.jsx
Performance: Memo verwenden
import { memo } from 'react'
// Heavy Component die nicht re-rendern soll
const ExpensiveComponent = memo(function ExpensiveComponent() {
return <div>Heavy calculations...</div>
})
Häufige Fehler
Fehler 1: Context ohne Provider
❌ FALSCH:
function Component() {
const value = useContext(MyContext) // undefined!
return <div>{value}</div>
}
✅ RICHTIG:
<MyContext.Provider value="data">
<Component />
</MyContext.Provider>
Fehler 2: Context zu oft verwenden
❌ FALSCH:
// Context für lokalen State
const [count, setCount] = useState(0)
<CountContext.Provider value={count}>
<Button />
</CountContext.Provider>
✅ RICHTIG:
// useState für lokalen State
function Component() {
const [count, setCount] = useState(0)
return <Button count={count} />
}
Fehler 3: Context Value direkt im Provider
❌ FALSCH:
// Re-renders alle Consumers bei jedem Render!
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
✅ RICHTIG:
// Value in Variable speichern
const value = { theme, setTheme }
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
// Oder useMemo
const value = useMemo(() => ({ theme, setTheme }), [theme])
Fehler 4: Zu viel in einen Context
❌ FALSCH:
// Alles in einem Context
const AppContext = createContext({
user, theme, language, cart, notifications, settings, ...
})
✅ RICHTIG:
// Mehrere kleine Contexts
<AuthContext.Provider>
<ThemeContext.Provider>
<CartContext.Provider>
{children}
</CartContext.Provider>
</ThemeContext.Provider>
</AuthContext.Provider>
Zusammenfassung
Du hast gelernt:
- ✅ Context API löst Props Drilling
- ✅ createContext() erstellt Context
- ✅ Provider teilt Daten
- ✅ useContext() holt Daten
- ✅ Custom Hooks für besseren Code
- ✅ Context + useReducer für komplexen State
- ✅ Mehrere Contexts kombinieren
Key Takeaways:
- Context für globalen State (Auth, Theme, etc.)
- Nicht für jeden State Context verwenden
- Custom Hooks erstellen (useAuth, useTheme)
- Provider in App-Root platzieren
- Performance: useMemo für value
- Kleine, fokussierte Contexts besser als ein großer
Wann Context verwenden:
- ✅ Theme/Dark Mode
- ✅ Authentication
- ✅ Sprache/i18n
- ✅ Shopping Cart
- ❌ Lokaler Component State
- ❌ Häufig ändernde Werte (Performance!)
Context vs Redux:
- Context: Einfacher, für kleinere Apps
- Redux: Komplexer, für große Apps mit viel State
Praktische Übungen
Übung 1: Theme Context
Erstelle einen Theme Context mit:
- Light/Dark Mode
- Toggle Button
- Persistierung in localStorage
Übung 2: Language Context
Implementiere Mehrsprachigkeit:
- DE/EN Umschalter
- Übersetzungen für Texte
- useLanguage Hook
Übung 3: Notification Context
Baue ein Notification System:
- addNotification()
- removeNotification()
- Timeout nach 3 Sekunden
- Toast Komponente
Übung 4: User Preferences
Erstelle einen Settings Context:
- fontSize (small/medium/large)
- notifications (on/off)
- autoSave (on/off)
- Speichere in localStorage
Übung 5: Combined Context
Kombiniere Auth + Theme + Language:
- AuthProvider
- ThemeProvider
- LanguageProvider
- Erstelle AppProvider der alle kombiniert
Gut gemacht! 🎉
Du hast "React Context API - Globaler State ohne Props Drilling" abgeschlossen
Artikel bewerten
Bitte einloggen um zu bewerten
Das könnte dich auch interessieren
React Custom Hooks - Wiederverwendbare Logik
Lerne Custom Hooks zu erstellen und Code zwischen Komponenten zu teilen. DRY-Prinzip in React mit eigenen Hooks!
React useEffect Hook - Side Effects & Lifecycle
Lerne den useEffect Hook für Side Effects, Data Fetching, Subscriptions und Component Lifecycle in React Function Components.
React useRef - Refs und DOM Manipulation
Lerne useRef für DOM-Zugriff, uncontrolled Forms, Animations und persistente Werte die kein Re-Render auslösen!