Fortgeschritten182025-01-15

React Context API - Globaler State ohne Props Drilling

Lerne die React Context API kennen und vermeide Props Drilling. Teile State einfach zwischen Komponenten ohne Redux!

#react#context-api#state-management#hooks#useContext

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 Drilling - Das 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

Context erstellen und verwenden
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

Context definieren
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

Provider - Daten bereitstellen
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

useContext Hook
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

Theme Context komplett
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

Authentifizierung mit Context
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

Multiple Providers
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

Warenkorb mit 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

Context + useReducer für komplexen State
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
ReactLektion 7 von 10
70% abgeschlossen
Lektion abgeschlossen!

Gut gemacht! 🎉

Du hast "React Context API - Globaler State ohne Props Drilling" abgeschlossen

Artikel bewerten

0.0 (0 Bewertungen)

Bitte einloggen um zu bewerten