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:¶
- Komponente rendert mit
disciplines = [](leer) - useEffect läuft:
disciplines.length === 0→ setztfilteredDisciplines = [] - Button ist disabled:
formData.disciplines.length === 0→true - Warnung wird NICHT angezeigt: Keine dedizierte Warnung-Komponente
- Aber: Browser/React zeigt irgendwie eine Meldung (vermutlich durch HTML5 validation)
Ablauf beim zweiten Öffnen:¶
- Komponente rendert mit
disciplines = [](leer) - API lädt Disziplinen →
disciplines = [...](gefüllt) - useEffect läuft:
disciplines.length > 0→ filtert korrekt - filteredDisciplines wird korrekt gefüllt
- 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¶
- Navigiere zu "Wettkämpfe"
- Klicke "Neuer Wettkampf"
- ✅ Erwartung: Keine Fehlermeldung beim ersten Öffnen
- ✅ Erwartung: Disziplinen laden und werden angezeigt
- ✅ Erwartung: Warnung erscheint NUR wenn keine Disziplin ausgewählt
Manueller Test 2: Wettkampf bearbeiten¶
- Navigiere zu "Wettkämpfe"
- Klicke "Bearbeiten" bei existierendem Wettkampf
- ✅ Erwartung: Keine Fehlermeldung beim ersten Öffnen
- ✅ Erwartung: Disziplinen laden und ausgewählte sind markiert
- ✅ Erwartung: Keine Warnung, wenn bereits Disziplinen ausgewählt
Manueller Test 3: Disziplinen abwählen¶
- Öffne Wettkampf mit Disziplinen
- Wähle alle Disziplinen ab
- ✅ Erwartung: Warnung "Mindestens eine Disziplin..." erscheint
- ✅ Erwartung: Submit-Button ist disabled
- Wähle eine Disziplin aus
- ✅ 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¶
- Race Conditions vermeiden: Immer explizit prüfen, ob Daten geladen sind
- Validierung zur richtigen Zeit: Nicht validieren, bevor Daten da sind
- Explizit statt implizit: Klare if/else Branches statt Fallback-Werte
- Benutzerfreundlichkeit: Keine Fehlermeldungen während Laden
- 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"