Zum Inhalt springen
React Anfänger 35 min

State Management

Verstehe State Management in React. Von lokalem State über useReducer bis zu globalen Lösungen - finde den richtigen Ansatz für dein Projekt.

Aktualisiert:

State Management

Je groesser deine App wird, desto wichtiger wird die Frage: Wo lebt der State? In diesem Tutorial lernst du verschiedene Strategien fuer State Management kennen – von einfach bis fortgeschritten.

State-Arten verstehen

ArtBeschreibungBeispielLoesung
Lokaler UI-StateState einer KomponenteToggle, Formular-InputuseState
Geteilter StateState fuer mehrere KomponentenFilter, SortierungState hochziehen
Globaler StateApp-weiter StateUser, Theme, SpracheContext / Zustand
Server StateDaten vom ServerAPI-Daten, CacheTanStack Query
URL StateState in der URLSuchbegriff, SeiteReact Router

Lokaler State mit useState

Fuer State, der nur eine Komponente betrifft:

function SearchInput() {
  const [query, setQuery] = useState('');

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Suchen..."
    />
  );
}

State hochziehen (Lifting State Up)

Wenn Geschwister-Komponenten denselben State brauchen, ziehe ihn zur gemeinsamen Eltern-Komponente hoch:

// VORHER: State in jedem Kind separat (nicht synchron)
function TemperatureInput() {
  const [temp, setTemp] = useState('');
  // ...
}

// NACHHER: State im Elternteil (synchron)
function TemperatureConverter() {
  const [celsius, setCelsius] = useState('');

  const fahrenheit = celsius ? (parseFloat(celsius) * 9/5 + 32).toFixed(1) : '';

  return (
    <div>
      <div>
        <label>Celsius</label>
        <input
          value={celsius}
          onChange={(e) => setCelsius(e.target.value)}
          type="number"
        />
      </div>
      <div>
        <label>Fahrenheit</label>
        <input value={fahrenheit} readOnly />
      </div>
    </div>
  );
}

useReducer fuer komplexen State

Wenn State-Updates komplex werden, nutze useReducer:

import { useReducer } from 'react';

const initialState = {
  items: [],
  filter: 'all',
  sortBy: 'date'
};

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        items: [...state.items, {
          id: Date.now(),
          text: action.payload,
          done: false,
          createdAt: new Date()
        }]
      };

    case 'TOGGLE_TODO':
      return {
        ...state,
        items: state.items.map(item =>
          item.id === action.payload
            ? { ...item, done: !item.done }
            : item
        )
      };

    case 'DELETE_TODO':
      return {
        ...state,
        items: state.items.filter(item => item.id !== action.payload)
      };

    case 'SET_FILTER':
      return { ...state, filter: action.payload };

    case 'SET_SORT':
      return { ...state, sortBy: action.payload };

    case 'CLEAR_DONE':
      return {
        ...state,
        items: state.items.filter(item => !item.done)
      };

    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  const [input, setInput] = useState('');

  // Berechnete Werte
  const filteredItems = state.items
    .filter(item => {
      if (state.filter === 'active') return !item.done;
      if (state.filter === 'done') return item.done;
      return true;
    })
    .sort((a, b) => {
      if (state.sortBy === 'name') return a.text.localeCompare(b.text);
      return b.createdAt - a.createdAt;
    });

  function handleAdd(e) {
    e.preventDefault();
    if (input.trim()) {
      dispatch({ type: 'ADD_TODO', payload: input.trim() });
      setInput('');
    }
  }

  return (
    <div>
      <form onSubmit={handleAdd}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Neues Todo..."
        />
        <button type="submit">Hinzufuegen</button>
      </form>

      <div>
        <button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'all' })}>
          Alle ({state.items.length})
        </button>
        <button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'active' })}>
          Aktiv ({state.items.filter(i => !i.done).length})
        </button>
        <button onClick={() => dispatch({ type: 'SET_FILTER', payload: 'done' })}>
          Erledigt ({state.items.filter(i => i.done).length})
        </button>
      </div>

      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>
            <span
              onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: item.id })}
              style={{
                textDecoration: item.done ? 'line-through' : 'none',
                cursor: 'pointer'
              }}
            >
              {item.text}
            </span>
            <button onClick={() => dispatch({ type: 'DELETE_TODO', payload: item.id })}>
              Loeschen
            </button>
          </li>
        ))}
      </ul>

      <button onClick={() => dispatch({ type: 'CLEAR_DONE' })}>
        Erledigte entfernen
      </button>
    </div>
  );
}

useState vs. useReducer

useStateuseReducer
Einfacher StateIdealOverkill
Komplexer StateUnuebersichtlichIdeal
Mehrere zusammenhaengende UpdatesSchwierigEinfach
TestbarkeitMittelHoch (Reducer ist pure Function)
DebuggingMittelGut (Action-Log moeglich)

Context + useReducer

Kombiniere beide fuer globalen State:

// store/AppContext.jsx
import { createContext, useContext, useReducer } from 'react';

const AppContext = createContext();

const initialState = {
  user: null,
  theme: 'light',
  notifications: [],
  cart: []
};

function appReducer(state, action) {
  switch (action.type) {
    case 'LOGIN':
      return { ...state, user: action.payload };
    case 'LOGOUT':
      return { ...state, user: null, cart: [] };
    case 'TOGGLE_THEME':
      return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
    case 'ADD_NOTIFICATION':
      return { ...state, notifications: [...state.notifications, action.payload] };
    case 'DISMISS_NOTIFICATION':
      return {
        ...state,
        notifications: state.notifications.filter(n => n.id !== action.payload)
      };
    case 'ADD_TO_CART':
      return { ...state, cart: [...state.cart, action.payload] };
    case 'CLEAR_CART':
      return { ...state, cart: [] };
    default:
      return state;
  }
}

export function AppProvider({ children }) {
  const [state, dispatch] = useReducer(appReducer, initialState);

  return (
    <AppContext.Provider value={{ state, dispatch }}>
      {children}
    </AppContext.Provider>
  );
}

export function useApp() {
  const context = useContext(AppContext);
  if (!context) throw new Error('useApp muss innerhalb AppProvider sein');
  return context;
}
// Verwendung in Komponenten
function Header() {
  const { state, dispatch } = useApp();

  return (
    <header>
      <button onClick={() => dispatch({ type: 'TOGGLE_THEME' })}>
        {state.theme === 'light' ? 'Dark Mode' : 'Light Mode'}
      </button>
      <span>Warenkorb: {state.cart.length}</span>
    </header>
  );
}

Zustand (Zustandsbibliothek)

Fuer groessere Apps bietet Zustand eine einfache Alternative:

npm install zustand
import { create } from 'zustand';

const useStore = create((set) => ({
  // State
  user: null,
  cart: [],
  theme: 'light',

  // Actions
  login: (user) => set({ user }),
  logout: () => set({ user: null, cart: [] }),

  addToCart: (item) => set((state) => ({
    cart: [...state.cart, item]
  })),

  removeFromCart: (id) => set((state) => ({
    cart: state.cart.filter(item => item.id !== id)
  })),

  toggleTheme: () => set((state) => ({
    theme: state.theme === 'light' ? 'dark' : 'light'
  }))
}));

// Verwendung - kein Provider noetig!
function CartButton() {
  const cart = useStore(state => state.cart);
  return <span>Warenkorb ({cart.length})</span>;
}

function ThemeToggle() {
  const { theme, toggleTheme } = useStore();
  return <button onClick={toggleTheme}>{theme}</button>;
}

Den richtigen Ansatz waehlen

Braucht nur EINE Komponente den State?
  → useState

Brauchen GESCHWISTER den State?
  → State in den Elternteil hochziehen

Ist der State KOMPLEX (viele Updates)?
  → useReducer

Braucht die GESAMTE App den State?
  → Context + useReducer ODER Zustand

Sind es SERVER-DATEN?
  → TanStack Query

Uebungen

  1. Refactore eine Todo-App von useState zu useReducer
  2. Erstelle einen globalen App-State mit Context + useReducer fuer Theme und User
  3. Probiere Zustand aus und baue einen einfachen Warenkorb-Store

Was kommt als Naechstes?

Du beherrschst jetzt State Management in React. In den naechsten Kapiteln setzt du dein Wissen in drei Praxisprojekten um – eine Portfolio-Seite, einen Online-Shop und eine Dashboard-App!

Zusammenfassung

  • Lokaler State (useState) fuer einzelne Komponenten
  • State hochziehen wenn Geschwister denselben State brauchen
  • useReducer fuer komplexen State mit vielen Aktionen
  • Context + useReducer fuer globalen App-State
  • Zustand als leichtgewichtige Alternative fuer groessere Apps
  • Server State gehoert in eine Bibliothek wie TanStack Query

Pro-Tipp: Starte immer mit dem einfachsten Ansatz (useState) und refactore erst, wenn du an Grenzen stoesst. “State Management” ist eines der Themen, wo Entwickler oft zu frueh zu komplexen Loesungen greifen. Erinnere dich: Der beste Code ist der, den du nicht brauchst!

Zurück zum React Kurs