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.
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
| Art | Beschreibung | Beispiel | Loesung |
|---|---|---|---|
| Lokaler UI-State | State einer Komponente | Toggle, Formular-Input | useState |
| Geteilter State | State fuer mehrere Komponenten | Filter, Sortierung | State hochziehen |
| Globaler State | App-weiter State | User, Theme, Sprache | Context / Zustand |
| Server State | Daten vom Server | API-Daten, Cache | TanStack Query |
| URL State | State in der URL | Suchbegriff, Seite | React 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
useState | useReducer | |
|---|---|---|
| Einfacher State | Ideal | Overkill |
| Komplexer State | Unuebersichtlich | Ideal |
| Mehrere zusammenhaengende Updates | Schwierig | Einfach |
| Testbarkeit | Mittel | Hoch (Reducer ist pure Function) |
| Debugging | Mittel | Gut (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
- Refactore eine Todo-App von useState zu useReducer
- Erstelle einen globalen App-State mit Context + useReducer fuer Theme und User
- 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
useReducerfuer 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!