Skip to content

Kapitel 15: Projekt - Counter

In diesem Kapitel bauen wir eine Counter-Anwendung, um React Hooks zu üben.


15.1 Anforderungsanalyse

✅ Funktionen

  1. ✅ Zähler erhöhen
  2. ✅ Zähler verringern
  3. ✅ Zähler zurücksetzen
  4. ✅ Schrittweite ändern
  5. ✅ Zähler speichern (LocalStorage)

🎨 UI-Struktur

┌─────────────────────────────┐
│        Counter             │
│  ┌─────────────────────┐  │
│  │                     │  │
│  │    Zähler: 10      │  │
│  │                     │  │
│  └─────────────────────┘  │
│  [+] [-] [Reset]         │
│  Schrittweite: [ 1 ]     │
└─────────────────────────────┘

15.2 useReducer implementieren

📦 useReducer Grundlagen

useReducer ist eine Alternative zu useState für komplexe Zustandslogik.

jsx
import { useReducer } from 'react';

// Reducer-Funktion
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'reset':
      return { ...state, count: 0 };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

function Counter() {
  const initialState = { count: 0, step: 1 };
  const [state, dispatch] = useReducer(counterReducer, initialState);

  return (
    <div className="counter">
      <h2>Zähler: {state.count}</h2>
      
      <div className="buttons">
        <button onClick={() => dispatch({ type: 'increment' })}>
          +
        </button>
        <button onClick={() => dispatch({ type: 'decrement' })}>
          -
        </button>
        <button onClick={() => dispatch({ type: 'reset' })}>
          Reset
        </button>
      </div>
      
      <div className="step">
        <label>Schrittweite:</label>
        <input 
          type="number" 
          value={state.step}
          onChange={(e) => dispatch({ 
            type: 'setStep', 
            payload: Number(e.target.value) 
          })}
        />
      </div>
    </div>
  );
}

export default Counter;

15.3 Custom Hook: useCounter

🔄 Benutzerdefinierter Hook

hooks/useCounter.js:

jsx
import { useReducer } from 'react';

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'reset':
      return { ...state, count: 0 };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

function useCounter(initialCount = 0, initialStep = 1) {
  const initialState = { count: initialCount, step: initialStep };
  const [state, dispatch] = useReducer(counterReducer, initialState);

  const increment = () => dispatch({ type: 'increment' });
  const decrement = () => dispatch({ type: 'decrement' });
  const reset = () => dispatch({ type: 'reset' });
  const setStep = (step) => dispatch({ type: 'setStep', payload: step });

  return {
    count: state.count,
    step: state.step,
    increment,
    decrement,
    reset,
    setStep
  };
}

export default useCounter;

📦 Verwendung

Counter.jsx:

jsx
import useCounter from './hooks/useCounter';

function Counter() {
  const { count, step, increment, decrement, reset, setStep } = useCounter();

  return (
    <div className="counter">
      <h2>Zähler: {count}</h2>
      
      <div className="buttons">
        <button onClick={increment}>+</button>
        <button onClick={decrement}>-</button>
        <button onClick={reset}>Reset</button>
      </div>
      
      <div className="step">
        <label>Schrittweite:</label>
        <input 
          type="number" 
          value={step}
          onChange={(e) => setStep(Number(e.target.value))}
        />
      </div>
    </div>
  );
}

export default Counter;

Vorteile:

  • Logik ist wiederverwendbar
  • Komponente ist einfacher (weniger Code)
  • Testing ist einfacher

15.4 Formular-Bindung (Schrittweite)

📝 Schrittweite ändern

jsx
function Counter() {
  const { count, step, increment, decrement, reset, setStep } = useCounter();

  const handleStepChange = (e) => {
    const value = Number(e.target.value);
    if (value > 0) {
      setStep(value);
    }
  };

  return (
    <div className="counter">
      <h2>Zähler: {count}</h2>
      
      <div className="buttons">
        <button onClick={increment}>+</button>
        <button onClick={decrement}>-</button>
        <button onClick={reset}>Reset</button>
      </div>
      
      <div className="step">
        <label>Schrittweite:</label>
        <input 
          type="number" 
          min="1"
          value={step}
          onChange={handleStepChange}
        />
      </div>
    </div>
  );
}

Wichtig:

  • value={step}: Aktueller Wert aus State
  • onChange={handleStepChange}: Aktualisiert State bei Änderung

15.5 Stile hinzufügen

🎨 CSS-Datei (Counter.css)

css
.counter {
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
  text-align: center;
  border: 1px solid #ddd;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.counter h2 {
  font-size: 2rem;
  margin-bottom: 20px;
  color: #333;
}

.buttons {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-bottom: 20px;
}

.buttons button {
  padding: 10px 20px;
  font-size: 1.2rem;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.buttons button:hover {
  background-color: #0056b3;
}

.step {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.step label {
  font-weight: bold;
}

.step input {
  width: 80px;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  text-align: center;
}

15.6 Vollständiger Code

App.jsx:

jsx
import Counter from './components/Counter';
import './App.css';

function App() {
  return (
    <div className="app">
      <h1>Counter-Projekt</h1>
      <Counter />
    </div>
  );
}

export default App;

Counter.jsx:

jsx
import { useReducer } from 'react';
import './Counter.css';

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'reset':
      return { ...state, count: 0 };
    case 'setStep':
      return { ...state, step: action.payload };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0, step: 1 });

  return (
    <div className="counter">
      <h2>Zähler: {state.count}</h2>
      
      <div className="buttons">
        <button onClick={() => dispatch({ type: 'increment' })}>
          +
        </button>
        <button onClick={() => dispatch({ type: 'decrement' })}>
          -
        </button>
        <button onClick={() => dispatch({ type: 'reset' })}>
          Reset
        </button>
      </div>
      
      <div className="step">
        <label>Schrittweite:</label>
        <input 
          type="number" 
          value={state.step}
          onChange={(e) => dispatch({ 
            type: 'setStep', 
            payload: Number(e.target.value) 
          })}
        />
      </div>
    </div>
  );
}

export default Counter;

📝 Zusammenfassung

In diesem Kapitel haben wir gelernt:

  • ✅ Eine Counter-Anwendung zu bauen
  • useReducer für komplexe Zustandslogik verwenden
  • ✅ Benutzerdefinierte Hooks (useCounter) erstellen
  • ✅ Formular-Bindung (Schrittweite ändern)
  • ✅ Stile hinzufügen

🎯 Nächste Schritte

Im nächsten Kapitel werden wir:

  • Unternehmensebene Projekt bauen (Blog-System)
  • React Router verwenden
  • Axios für API-Aufrufe verwenden

Bereit für das nächste Projekt? → Kapitel 16: Projekt - Blog-System

Frei für alle Anfänger