fix(web): t-Falle in acht Ladeeffekten entschaerft, RssFeedListForm/SavedSearchBar-Ladefunktionen stabilisiert

Befunde 7-15 der Biome-Regel useExhaustiveDependencies (quick-260921-gof):

- VehicleTable, TenderDetail, DigestIntervalForm, SourceConfigForm,
  favorites-widget: Ersatz-Fehlertext aus useTranslations wird jetzt vor
  dem Effekt/Rueckruf in eine Konstante gezogen und diese Konstante in
  die Abhaengigkeitsliste aufgenommen - `t` selbst kommt nirgends mehr
  in eine Liste. In diesem Projekt ist belegt, dass `t` bei jedem
  Durchlauf eine frische Funktion ist (Testattrappen), eine `t`-
  Abhaengigkeit haette den jeweiligen Mount-Abruf zur Schleife gemacht.
- RssFeedListForm.tsx und SavedSearchBar.tsx: die Ladefunktionen waren
  gewoehnliche Funktionen im Rumpf (bei jedem Durchlauf neu) - jetzt in
  einen stabilen Rueckruf mit der Text-Konstante als einziger
  Abhaengigkeit eingepackt.
- ResultsList.tsx: Befund 8 (t) wie oben, Befund 15 (refreshKey) in den
  Effekt verschoben, der `load` aufruft, statt in `load` selbst zu
  stehen - eine begruendete `biome-ignore`-Zeile (erste im Projekt)
  haelt fest, dass der Auffrisch-Ausloeser der Elternseite ohne diese
  Abhaengigkeit wirkungslos waere.
- Sieben Testdateien um eine Zaehlprobe erweitert: erneutes Zeichnen mit
  unveraenderten Props darf keinen weiteren Abruf ausloesen; ResultsList
  zusaetzlich um eine Probe, dass ein refreshKey-Bump genau einen
  weiteren Abruf ausloest.
- DigestIntervalForm hat keine Testdatei - nur am laufenden System auf
  Meine Quellen geprueft (siehe SUMMARY).
- Wirkungslose eslint-disable-Zeilen fuer diese Regel entfallen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 12:29:05 +02:00
parent b3f0e3cdcd
commit e2c508cff5
15 changed files with 210 additions and 33 deletions
@@ -293,4 +293,19 @@ describe('VehicleTable', () => {
expect(screen.getByText('errors.loadVehiclesFailed')).toBeInTheDocument();
});
});
it('quick-260921-gof (Befund 7): erneutes Zeichnen ohne Prop-Aenderung loest KEINEN weiteren Abruf aus — die Uebersetzungs-Attrappe liefert bei jedem Durchlauf eine frische Funktion, eine Rueckkehr von `t` in die Abhaengigkeitsliste faellt hier sofort auf', async () => {
mockFetchVehicles.mockResolvedValue([]);
const { VehicleTable } = await import(
'@/app/(portal)/modules/dkv-fleet/settings/components/VehicleTable'
);
const { rerender } = render(<VehicleTable />);
await waitFor(() => expect(mockFetchVehicles).toHaveBeenCalledTimes(1));
rerender(<VehicleTable />);
await waitFor(() => expect(mockFetchVehicles).toHaveBeenCalledTimes(1));
});
});
@@ -179,18 +179,24 @@ export function VehicleTable() {
toastTimerRef.current = setTimeout(() => setToast(null), 4000);
}, []);
// Befund 7 (quick-260921-gof): `t` selbst kommt nicht in die Liste — die
// Uebersetzungs-Attrappe in den Tests liefert bei jedem Durchlauf eine
// frische Funktion, das wuerde `load` neu erzeugen und den Mount-Effekt
// unten (useEffect(() => load(), [load])) zu einer Abruf-Schleife machen.
// Stattdessen wird der Text vorgezogen: als Zeichenkette vergleicht React
// per Wert, die Liste bleibt ueber Durchlaeufe hinweg gleich.
const loadErrorText = t('errors.loadVehiclesFailed');
const load = useCallback(async () => {
try {
const data = await fetchVehicles();
setVehicles(data);
} catch {
setTableError(t('errors.loadVehiclesFailed'));
setTableError(loadErrorText);
} finally {
setIsLoading(false);
}
}, []); // `t` intentionally excluded: `load` is a dependency of the
// mount-time useEffect below, and an unstable `t` identity would turn
// that into a fetch-on-every-render loop instead of fetch-once-on-mount.
}, [loadErrorText]);
useEffect(() => {
load();