Skip to content

Kapitel 11: Zustandsverwaltung

In größeren Anwendungen wird Zustandsverwaltung unerlässlich, um Daten zwischen Komponenten zu teilen.


11.1 Warum Zustandsverwaltung?

🤔 Problem ohne Zustandsverwaltung

App
├── Header (benötigt user-State)
├── Main (benötigt user-State)
└── Footer (benötigt user-State)

Problem: Jede Komponente benötigt den gleichen State → Props-Drilling.

✅ Lösung: Zustandsverwaltung

  • State wird global gespeichert
  • Alle Komponenten können darauf zugreifen

11.2 Drei Lösungsansätze

1️⃣ useReducer + useContext (Leichtgewichtig)

Geeignet für: Kleine bis mittlere Projekte

Vorteile:

  • Keine zusätzliche Bibliothek erforderlich
  • Einfach zu verstehen

Nachteile:

  • Schwer zu skalieren
  • Keine "Time Travel" Debugging

2️⃣ Redux Toolkit (RTK) - Offiziell empfohlen

Geeignet für: Große Projekte mit komplexem State

Vorteile:

  • Offiziell von Redux-Team empfohlen
  • Vereinfacht traditionelles Redux
  • Hervorragendes DevTools

Nachteile:

  • Steile Lernkurve
  • Mehr Boilerplate-Code

3️⃣ Zustand - Leichtgewichtig und einfach

Geeignet für: Mittlere Projekte, die Einfachheit bevorzugen

Vorteile:

  • Sehr einfach und intuitiv
  • Wenig Boilerplate-Code
  • Gute Performance

Nachteile:

  • Kleinere Community als Redux
  • Weniger Tutorials

11.3 Lösung 1: useReducer + useContext

📦 Store erstellen

store.js:

jsx
import { createContext, useContext, useReducer } from 'react';

// 1. Initialzustand
const initialState = {
  user: null,
  isLoggedIn: false
};

// 2. Reducer-Funktion
function reducer(state, action) {
  switch (action.type) {
    case 'LOGIN':
      return { ...state, user: action.payload, isLoggedIn: true };
    case 'LOGOUT':
      return { ...state, user: null, isLoggedIn: false };
    default:
      return state;
  }
}

// 3. Context erstellen
const StoreContext = createContext();

// 4. Provider-Komponente
export function StoreProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);

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

// 5. Custom Hook für einfachen Zugriff
export function useStore() {
  return useContext(StoreContext);
}

🏠 Store in App verwenden

App.jsx:

jsx
import { StoreProvider } from './store';

function App() {
  return (
    <StoreProvider>
      <Header />
      <Main />
    </StoreProvider>
  );
}

📥 State in Komponenten verwenden

Header.jsx:

jsx
import { useStore } from './store';

function Header() {
  const { state, dispatch } = useStore();

  return (
    <div>
      {state.isLoggedIn ? (
        <>
          <p>Willkommen, {state.user.name}!</p>
          <button onClick={() => dispatch({ type: 'LOGOUT' })}>
            Logout
          </button>
        </>
      ) : (
        <button onClick={() => dispatch({ type: 'LOGIN', payload: { name: 'Max' } })}>
          Login
        </button>
      )}
    </div>
  );
}

11.4 Lösung 2: Redux Toolkit (RTK)

📦 Installation

bash
# npm
npm install @reduxjs/toolkit react-redux

# pnpm (empfohlen)
pnpm add @reduxjs/toolkit react-redux

🔧 Konfiguration (Schritt für Schritt)

Schritt 1: Slice erstellen

userSlice.js:

jsx
import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: {
    user: null,
    isLoggedIn: false
  },
  reducers: {
    login: (state, action) => {
      state.user = action.payload;
      state.isLoggedIn = true;
    },
    logout: (state) => {
      state.user = null;
      state.isLoggedIn = false;
    }
  }
});

export const { login, logout } = userSlice.actions;
export default userSlice.reducer;

Schritt 2: Store konfigurieren

store.js:

jsx
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';

export const store = configureStore({
  reducer: {
    user: userReducer
  }
});

Schritt 3: Provider bereitstellen

App.jsx:

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

function App() {
  return (
    <Provider store={store}>
      <Header />
      <Main />
    </Provider>
  );
}

Schritt 4: State verwenden

Header.jsx:

jsx
import { useSelector, useDispatch } from 'react-redux';
import { login, logout } from './userSlice';

function Header() {
  const user = useSelector((state) => state.user.user);
  const isLoggedIn = useSelector((state) => state.user.isLoggedIn);
  const dispatch = useDispatch();

  return (
    <div>
      {isLoggedIn ? (
        <>
          <p>Willkommen, {user.name}!</p>
          <button onClick={() => dispatch(logout())}>Logout</button>
        </>
      ) : (
        <button onClick={() => dispatch(login({ name: 'Max' }))}>
          Login
        </button>
      )}
    </div>
  );
}

11.5 Lösung 3: Zustand

📦 Installation

bash
# npm
npm install zustand

# pnpm (empfohlen)
pnpm add zustand

🔧 Store erstellen

store.js:

javascript
import { create } from 'zustand';

export const useStore = create((set) => ({
  user: null,
  isLoggedIn: false,
  
  login: (user) => set({ user, isLoggedIn: true }),
  logout: () => set({ user: null, isLoggedIn: false })
}));

📥 State verwenden

Header.jsx:

jsx
import { useStore } from './store';

function Header() {
  const { user, isLoggedIn, login, logout } = useStore();

  return (
    <div>
      {isLoggedIn ? (
        <>
          <p>Willkommen, {user.name}!</p>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <button onClick={() => login({ name: 'Max' })}>
          Login
        </button>
      )}
    </div>
  );
}

Vorteile von Zustand:

  • Kein Provider-Wrapper erforderlich
  • Sehr einfach zu verwenden
  • Gute Performance

11.6 Zustandspersistenz (LocalStorage)

💾 State in LocalStorage speichern

Beispiel mit Zustand:

javascript
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

export const useStore = create(
  persist(
    (set) => ({
      user: null,
      isLoggedIn: false,
      
      login: (user) => set({ user, isLoggedIn: true }),
      logout: () => set({ user: null, isLoggedIn: false })
    }),
    {
      name: 'user-storage', // LocalStorage-Schlüssel
    }
  )
);

Beispiel mit Redux Toolkit:

javascript
// store.js
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

const persistConfig = {
  key: 'root',
  storage
};

const persistedReducer = persistReducer(persistConfig, userReducer);

export const store = configureStore({
  reducer: persistedReducer
});

export const persistor = persistStore(store);

📝 Zusammenfassung

In diesem Kapitel haben wir gelernt:

  • ✅ Warum Zustandsverwaltung wichtig ist
  • ✅ Drei Lösungsansätze (useReducer+useContext, RTK, Zustand)
  • ✅ useReducer + useContext (leichtgewichtig)
  • ✅ Redux Toolkit (RTK) Installation und Verwendung
  • ✅ Zustand (einfach und leichtgewichtig)
  • ✅ Zustandspersistenz (LocalStorage)

🎯 Nächste Schritte

Im nächsten Kapitel werden wir lernen:

  • Netzwerk-Anfragen mit axios
  • API-Calls in React
  • Daten abrufen und anzeigen

Bereit für Netzwerk-Anfragen? → Kapitel 12: Netzwerk-Anfragen

Frei für alle Anfänger