--- id: PROJ-78 title: SearchResultsTable re-rendert bei jedem Tastenanschlag im Suchfeld status: Deployed created: 2026-08-05 --- ## Problem `SearchResultsTable` re-rendert bei jedem Tastenanschlag im Suchfeld, da `query` in `SearchPage` liegt und die Tabelle nicht memoisiert ist. Bei `page_size` 100 sind das 100 Zeilen inkl. `sanitizeSnippet`-Aufruf pro Tastendruck. `React.memo` allein würde hier nichts bringen: `onPageChange={(p) => doSearch(p)}` ist inline definiert, und `doSearch` selbst ist ein `useCallback` mit allen Filter-States in den Dependencies — die Memo-Prüfung würde bei jedem Tastendruck ohnehin fehlschlagen, weil sich die Callback-Referenz ändert. Ein sauberer Fix bräuchte einen Ref-basierten Umbau von `useSearch` (z.B. `doSearch` per `useRef` stabil halten oder Debounce am Input statt an jedem State-Update). Das ist eine Architekturänderung des Such-Hooks und war im Rahmen der reinen Crash-Härtung-Session ausgeschlossen. Nur relevant bei großen Ergebnislisten/schwacher Client-Hardware — kein Crash-Risiko. ## Lösung (Vorschlag) 1. `useSearch`: `doSearch` stabil halten, z.B. per `useRef` für die Filter-State-Werte statt sie als `useCallback`-Dependencies zu führen, oder 2. Debounce direkt am Such-Input (z.B. 300ms) statt bei jedem Tastendruck sofort neu zu rendern/suchen — reduziert sowohl Re-Renders als auch API-Calls. 3. `SearchResultsTable` mit `React.memo` + stabilen Callback-Referenzen umschließen, sobald 1 oder 2 umgesetzt ist. ## Implementation Notes Umgesetzt wurde eine Kombination aus Vorschlag 1–3, bewusst minimal gehalten (kein Context, kein Reducer-Umbau): **1. Debounce am Suchfeld — `src/components/search/SearchFilterBar.tsx:96-124`** Der Suchbegriff wird jetzt lokal in der Filterbar gehalten (`localQuery`) und nur debounced (300 ms) per `setQuery` nach oben propagiert. Ein `syncedRef` merkt sich den zuletzt zwischen Parent und Input abgeglichenen Wert, damit externe Änderungen (gespeicherte Suche anwenden) weiterhin ins Feld durchschlagen, ohne eine Rückkopplungsschleife zu erzeugen. Wirkung: State-Update in `SearchPage` passiert max. alle 300 ms statt pro Tastendruck — die gesamte Seite inkl. Tabelle rendert entsprechend seltener. **2. `doSearch` referenzstabil — `src/hooks/useSearch.ts:25-71`** Die Filterwerte werden in `filtersRef` gespiegelt; `doSearch` hat jetzt leere Dependencies und liest ausschließlich `filtersRef.current`. Damit ändert sich die Callback-Referenz nie mehr. `setQuery` ist ein Wrapper (`useSearch.ts:47-50`), der den Wert **synchron** in den Ref schreibt und erst dann den State setzt — dadurch verwendet ein unmittelbar folgendes `doSearch(1)` bereits den neuen Suchbegriff. **3. `React.memo` — `src/components/search/SearchResultsTable.tsx:65,190`** Komponente in `SearchResultsTableInner` umbenannt und als `memo(SearchResultsTableInner)` exportiert. In `src/app/search/page.tsx:245` wurde das inline `onPageChange={(p) => doSearch(p)}` durch `onPageChange={doSearch}` ersetzt. Alle Props der Tabelle (`results`, `selected`, `setSelected`, `total`, `page`, `totalPages`, `onPageChange`) sind beim Tippen jetzt referenzstabil → der Memo-Vergleich greift, die 100 Zeilen inkl. `sanitizeSnippet` werden nicht neu gerendert. **Enter-Verhalten bewusst erhalten:** `handleSubmit` in der Filterbar flusht den Debounce (`setQuery(localQuery)`) vor dem Aufruf von `onSubmit` — Enter direkt nach dem Tippen sucht weiterhin sofort mit dem gerade eingegebenen Begriff. **Nicht geändert:** Die übrigen Filter (Von/An/Datum/Sortierung/Anhang) bleiben unverändert direkt-kontrolliert (kein Debounce), Pagination, gespeicherte Suchen, Export- und Upload-Dialoge, `useSavedSearches` und der Initial-Load-Effekt in `useSearch`. Verifikation: `npm run build` (inkl. TypeScript) erfolgreich. Deployed auf 131 (Produktiv) am 2026-08-05. ## Acceptance Criteria - [x] Tippen im Suchfeld löst kein Re-Render der Ergebnistabelle mehr aus, solange keine neue Suche ausgelöst wurde. (Doppelt abgesichert: Debounce verhindert das State-Update in `SearchPage`, `React.memo` + stabile Props fangen den verbleibenden Fall ab.) - [x] Bestehendes Verhalten (Suche bei Enter/Debounce-Ende, Pagination) funktional unverändert — Enter flusht den Debounce vor der Suche. - [ ] Messbare Verbesserung (z.B. React DevTools Profiler) bei `page_size` 100. **Nicht automatisiert durchführbar** — React DevTools Profiler ist im Agent-Kontext nicht verfügbar. Begründung per Code-Analyse siehe Implementation Notes; manuelle Messung offen für QA.