Fix: "Mindestens eine Disziplin muss ausgewählt werden" beim ersten Öffnen

Problem

Beim ersten Öffnen des "Wettkampf bearbeiten" Dialogs erschien die Fehlermeldung "Mindestens eine Disziplin muss ausgewählt werden", obwohl noch keine Disziplinen geladen waren. Beim zweiten Öffnen war die Meldung weg.

Ursache

Das Problem war ein Race Condition zwischen zwei useEffect-Hooks:

Ursprüngliche Logik:

// useEffect 1: Filter disciplines based on gender
useEffect(() => {
  if (formData.gender && disciplines.length > 0) {
    const filtered = disciplines.filter(/* ... */);
    setFilteredDisciplines(filtered);
  } else {
    setFilteredDisciplines(disciplines); // ← Problem: Wird bei disciplines.length === 0 ausgeführt
  }
}, [formData.gender, disciplines]);

// Button Validation
disabled={saving || formData.disciplines.length === 0} // ← Prüft sofort beim Rendern

Ablauf beim ersten Öffnen:

  1. Komponente rendert mit disciplines = [] (leer)
  2. useEffect läuft: disciplines.length === 0 → setzt filteredDisciplines = []
  3. Button ist disabled: formData.disciplines.length === 0true
  4. Warnung wird NICHT angezeigt: Keine dedizierte Warnung-Komponente
  5. Aber: Browser/React zeigt irgendwie eine Meldung (vermutlich durch HTML5 validation)

Ablauf beim zweiten Öffnen:

  1. Komponente rendert mit disciplines = [] (leer)
  2. API lädt Disziplinendisciplines = [...] (gefüllt)
  3. useEffect läuft: disciplines.length > 0 → filtert korrekt
  4. filteredDisciplines wird korrekt gefüllt
  5. Keine Warnung, weil Disziplinen vorhanden sind

Lösung

1. Verbesserter Filter-useEffect

useEffect(() => {
  if (disciplines.length > 0) {
    if (formData.gender) {
      const filtered = disciplines.filter(/* ... */);
      setFilteredDisciplines(filtered);
    } else {
      setFilteredDisciplines(disciplines);
    }
  } else {
    // ✅ NEU: Explizit leere Liste, wenn noch nicht geladen
    setFilteredDisciplines([]);
  }
}, [formData.gender, disciplines]);

Verbesserung: - Explizite Behandlung des "noch nicht geladen" Falls - Keine implizite Zuweisung von disciplines wenn leer - Klarere Logik: "Wenn Disziplinen geladen → filtern, sonst → leer lassen"

2. Intelligente Button-Validierung

// ALT: Disabled, sobald keine Disziplinen ausgewählt
disabled={saving || formData.disciplines.length === 0}

// NEU: Disabled nur, wenn Disziplinen GELADEN sind UND keine ausgewählt
disabled={saving || (disciplines.length > 0 && formData.disciplines.length === 0)}

Verbesserung: - Button ist nur disabled, wenn wir WISSEN, dass Disziplinen da sind - Verhindert falsche Warnung beim Laden - Klare Unterscheidung: "Lädt noch" vs. "Keine ausgewählt"

3. Explizite Warnung-Komponente

{disciplines.length > 0 && formData.disciplines.length === 0 && (
  <div className="p-3 bg-amber-50 border border-amber-200 rounded-lg text-sm text-amber-800">
    ⚠️ Mindestens eine Disziplin muss ausgewählt werden
  </div>
)}

Verbesserung: - Warnung wird NUR angezeigt, wenn: - Disziplinen geladen sind (disciplines.length > 0) - UND keine Disziplin ausgewählt ist (formData.disciplines.length === 0) - Visuell konsistent (gelber Warnkasten) - Klare Benutzerführung

Vorher/Nachher Vergleich

Vorher (Beim ersten Öffnen):

1. Dialog öffnet → disciplines = []
2. useEffect: else-Branch → filteredDisciplines = []
3. Button disabled: true (wegen formData.disciplines.length === 0)
4. ❌ Fehlermeldung erscheint irgendwie
5. Nutzer sieht: "Mindestens eine Disziplin" aber keine Disziplinen zum Auswählen

Nachher (Beim ersten Öffnen):

1. Dialog öffnet → disciplines = []
2. useEffect: else-Branch (explizit) → filteredDisciplines = []
3. Button disabled: false (weil disciplines.length === 0, also noch am Laden)
4. ✅ Keine Warnung (Bedingung: disciplines.length > 0 nicht erfüllt)
5. API lädt → disciplines = [...]
6. useEffect: filtert korrekt → filteredDisciplines = [...]
7. Button disabled: true (nur wenn keine ausgewählt)
8. ✅ Warnung erscheint NUR wenn Disziplinen da sind aber keine ausgewählt

Technische Details

Race Condition Vermeidung

Problem: Zwei asynchrone Prozesse: - API-Aufruf lädt Disziplinen (async) - useEffect reagiert auf disciplines Änderung (sync)

Lösung: Explizite Zustandsverwaltung mit klaren Bedingungen:

if (disciplines.length > 0) {
  // Disziplinen geladen → normale Logik
} else {
  // Disziplinen NICHT geladen → warten
}

Validierung zur richtigen Zeit

Vorher: Validierung läuft sofort beim ersten Render Nachher: Validierung wartet, bis Daten geladen sind

Benutzerfreundlichkeit

  • ✅ Keine verwirrende Fehlermeldung beim Laden
  • ✅ Warnung erscheint zur richtigen Zeit
  • ✅ Button bleibt klickbar während Laden (wird dann disabled wenn nötig)
  • ✅ Visuelles Feedback ist konsistent

Tests

Manueller Test 1: Neuer Wettkampf

  1. Navigiere zu "Wettkämpfe"
  2. Klicke "Neuer Wettkampf"
  3. Erwartung: Keine Fehlermeldung beim ersten Öffnen
  4. Erwartung: Disziplinen laden und werden angezeigt
  5. Erwartung: Warnung erscheint NUR wenn keine Disziplin ausgewählt

Manueller Test 2: Wettkampf bearbeiten

  1. Navigiere zu "Wettkämpfe"
  2. Klicke "Bearbeiten" bei existierendem Wettkampf
  3. Erwartung: Keine Fehlermeldung beim ersten Öffnen
  4. Erwartung: Disziplinen laden und ausgewählte sind markiert
  5. Erwartung: Keine Warnung, wenn bereits Disziplinen ausgewählt

Manueller Test 3: Disziplinen abwählen

  1. Öffne Wettkampf mit Disziplinen
  2. Wähle alle Disziplinen ab
  3. Erwartung: Warnung "Mindestens eine Disziplin..." erscheint
  4. Erwartung: Submit-Button ist disabled
  5. Wähle eine Disziplin aus
  6. Erwartung: Warnung verschwindet, Button ist enabled

Edge Cases

  • Langsame API: Disziplinen laden langsam → Keine Fehlermeldung während Laden
  • Leere API: Keine Disziplinen verfügbar → "Loading disciplines..." angezeigt
  • Gender-Wechsel: Gender ändern → Disziplinen neu filtern → Auswahl bleibt erhalten wenn möglich

Betroffene Dateien

client/src/components/EditCompetition.tsx: - Zeile 83-102: Filter-useEffect verbessert - Zeile 6: CheckCircle Import hinzugefügt - Zeile 462-471: Warnung und Button-Validierung

Lessons Learned

  1. Race Conditions vermeiden: Immer explizit prüfen, ob Daten geladen sind
  2. Validierung zur richtigen Zeit: Nicht validieren, bevor Daten da sind
  3. Explizit statt implizit: Klare if/else Branches statt Fallback-Werte
  4. Benutzerfreundlichkeit: Keine Fehlermeldungen während Laden
  5. Debug-Logs beibehalten: Helfen beim Verstehen der Ausführungsreihenfolge

Verwandte Issues

Ähnliche Probleme können auftreten bei: - Jeder Komponente mit async Datenladung + Validierung - Komponenten mit abhängigen useEffects - Formularen, die sofort nach Laden validieren

Prävention für die Zukunft

Pattern für Datenladung + Validierung:

const [data, setData] = useState<T[]>([]);
const [isDataLoaded, setIsDataLoaded] = useState(false);

useEffect(() => {
  loadData().then(result => {
    setData(result);
    setIsDataLoaded(true); // ✅ Expliziter Flag
  });
}, []);

// Validierung NUR wenn Daten geladen
const isValid = isDataLoaded && data.length > 0;
const showWarning = isDataLoaded && data.length === 0;

Vorteile: - Expliziter Ladezustand - Keine Race Conditions - Klare Trennung: "Lädt" vs. "Leer" vs. "Gefüllt"