Skip to content

Kapitel 8: State Management

📖 Lernziele

In diesem Kapitel lernen Sie:

  • ✅ Client-Side State Management (useState, useReducer)
  • ✅ Globalen State mit Context API verwalten
  • ✅ Redux Toolkit in Next.js integrieren
  • ✅ Zustand als leichtgewichtige Alternative verwenden
  • ✅ Server-State mit SWR/React Query verwalten
  • ✅ LocalStorage für Persistenz verwenden

8.1 Client-Side State Management

🎯 useState (Lokaler State)

Beispiel: Einfacher Zähler

jsx
'use client';

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Zähler: {count}</p>
      <button onClick={() => setCount(count + 1)}>Erhöhen</button>
    </div>
  );
}

🎯 useReducer (Komplexer State)

Beispiel: Todo-Liste

jsx
'use client';

import { useReducer } from 'react';

// Reducer Funktion
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, { id: Date.now(), text: action.payload }];
    case 'DELETE':
      return state.filter(todo => todo.id !== action.payload);
    default:
      return state;
  }
}

export default function TodoList() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const [text, setText] = useState('');
  
  const addTodo = () => {
    if (text.trim()) {
      dispatch({ type: 'ADD', payload: text });
      setText('');
    }
  };
  
  return (
    <div>
      <input 
        value={text} 
        onChange={e => setText(e.target.value)} 
      />
      <button onClick={addTodo}>Hinzufügen</button>
      
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            {todo.text}
            <button onClick={() => dispatch({ type: 'DELETE', payload: todo.id })}>
              Löschen
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

8.2 Context API (Globaler State)

🎯 Wann Context API verwenden?

  • ✅ Wenn Daten für viele Komponenten benötigt werden (Theme, User-Auth)
  • ✅ Mittelgroße Anwendungen (kein riesiger State)
  • ✅ Einfache Implementierung

📝 Context erstellen

Datei: context/ThemeContext.js

jsx
'use client';

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme muss innerhalb von ThemeProvider verwendet werden');
  }
  return context;
}

📝 Verwendung in Layout

Datei: app/layout.js

jsx
import { ThemeProvider } from '@/context/ThemeContext';

export default function RootLayout({ children }) {
  return (
    <html lang="de">
      <body>
        <ThemeProvider>
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

📝 Verwendung in Komponenten

Datei: components/ThemeToggle.js

jsx
'use client';

import { useTheme } from '@/context/ThemeContext';

export default function ThemeToggle() {
  const { theme, toggleTheme } = useTheme();
  
  return (
    <button onClick={toggleTheme}>
      Aktuelles Theme: {theme}
    </button>
  );
}

8.3 Redux Toolkit (Komplexe Anwendungen)

🎯 Installation

bash
pnpm add @reduxjs/toolkit react-redux

📝 Store konfigurieren

Datei: lib/store.js

javascript
import { configureStore } from '@reduxjs/toolkit';

export const makeStore = () => {
  return configureStore({
    reducer: {
      // Fügen Sie hier Ihre Reducer hinzu
    },
  });
};

export const store = makeStore();

📝 Provider in Layout einbinden

Datei: app/layout.js

jsx
import { store } from '@/lib/store';
import { Provider } from 'react-redux';

export default function RootLayout({ children }) {
  return (
    <html lang="de">
      <body>
        <Provider store={store}>
          {children}
        </Provider>
      </body>
    </html>
  );
}

8.4 Zustand (Leichtgewichtig)

🎯 Installation

bash
pnpm add zustand

📝 Store erstellen

Datei: store/useStore.js

javascript
import { create } from 'zustand';

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

export default useStore;

📝 Verwendung

jsx
'use client';

import useStore from '@/store/useStore';

export default function Counter() {
  const { count, increment, decrement, reset } = useStore();
  
  return (
    <div>
      <p>Zähler: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

Vorteile von Zustand:

  • ✅ Sehr einfach zu lernen
  • ✅ Kein Boilerplate-Code
  • ✅ Gute Performance

8.5 Server-State (SWR/React Query)

🎯 SWR für Server-Daten

Installation:

bash
pnpm add swr

Verwendung:

jsx
'use client';

import useSWR from 'swr';

const fetcher = (url) => fetch(url).then(res => res.json());

export default function UserProfile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher);
  
  if (isLoading) return <p>Lädt...</p>;
  if (error) return <p>Fehler beim Laden</p>;
  
  return (
    <div>
      <h1>{data.name}</h1>
      <p>Email: {data.email}</p>
    </div>
  );
}

Vorteile:

  • ✅ Automatisches Caching
  • ✅ Automatische Revalidierung
  • ✅ Optimistische Updates

8.6 LocalStorage Persistenz

🎯 Daten im Browser speichern

Beispiel: Warenkorb speichern

jsx
'use client';

import { useState, useEffect } from 'react';

export default function ShoppingCart() {
  const [cart, setCart] = useState([]);
  
  // Beim Laden aus LocalStorage lesen
  useEffect(() => {
    const savedCart = localStorage.getItem('cart');
    if (savedCart) {
      setCart(JSON.parse(savedCart));
    }
  }, []);
  
  // Bei Änderung in LocalStorage speichern
  useEffect(() => {
    localStorage.setItem('cart', JSON.stringify(cart));
  }, [cart]);
  
  const addToCart = (item) => {
    setCart([...cart, item]);
  };
  
  return (
    <div>
      <h2>Warenkorb ({cart.length})</h2>
      {/* ... */}
    </div>
  );
}

🎯 Cookies (Server-Side)

In Server Components:

jsx
import { cookies } from 'next/headers';

export default function Page() {
  const cookieStore = cookies();
  const token = cookieStore.get('token');
  
  return <div>Token: {token?.value}</div>;
}

📝 Zusammenfassung

In diesem Kapitel haben Sie gelernt:

MethodeEinsatzgebietKomplexität
useStateLokaler StateEinfach ✅
useReducerKomplexer lokaler StateMittel ⭐
Context APIGlobaler State (klein/mittel)Mittel ⭐
Redux ToolkitGlobaler State (groß)Komplex ⚠️
ZustandGlobaler State (einfach)Einfach ✅
SWR/React QueryServer-StateMittel ⭐
LocalStoragePersistenz (Client)Einfach ✅

✅ Nächste Schritte

  1. Übung: Implementieren Sie einen globalen State mit Context API
  2. Übung: Erstellen Sie einen Warenkorb mit Zustand
  3. Weiter geht's: Kapitel 9 - Styling-Lösungen

🎯 Selbsttest

Frage 1: Wann sollten Sie Context API verwenden?

Antwort anzeigen Wenn Daten für viele Komponenten benötigt werden (Theme, Auth) und die Anwendung mittelgroß ist.

Frage 2: Was ist der Vorteil von Zustand gegenüber Redux?

Antwort anzeigen Zustand ist viel einfacher zu lernen und benötigt weniger Boilerplate-Code.

Frage 3: Wofür wird SWR/React Query verwendet?

Antwort anzeigen Für die Verwaltung von Server-State (Daten, die von einer API geladen werden).

🚀 Weiter zu Kapitel 9: Styling-Lösungen

Frei für alle Anfänger