diff --git a/src/App.jsx b/src/App.jsx index 7e59bd2..930a6c6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -98,17 +98,21 @@ function DatasetApp({ dataset }) { [db], ); - // Run initial URL query once DB is ready + // Hydrate a ?q= deep link once, as soon as the database is ready. Fires at + // most once per mount, so it cannot cascade. useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect if (db && query) handleSearch(query); // eslint-disable-next-line react-hooks/exhaustive-deps }, [db]); - // Fetch total count for footer once DB loads + // Read the candidate count for the footer once the database loads. One-shot + // per mount; the query result cannot change without a new database. useEffect(() => { if (!db) return; const stmt = db.prepare("SELECT COUNT(*) AS c FROM student"); stmt.step(); + // eslint-disable-next-line react-hooks/set-state-in-effect setTotalCount(stmt.getAsObject().c); stmt.free(); }, [db]); diff --git a/src/components/custom-query.jsx b/src/components/custom-query.jsx index 99fcba5..21b7af5 100644 --- a/src/components/custom-query.jsx +++ b/src/components/custom-query.jsx @@ -82,6 +82,7 @@ export function CustomQuery({ db, disabled, presets = [] }) { // sees the student columns instead of a blank textarea. useEffect(() => { if (db && schemaPreset && columns.length === 0 && !sql) { + // eslint-disable-next-line react-hooks/set-state-in-effect executeQuery(schemaPreset.sql); setSql(schemaPreset.sql); } diff --git a/src/components/search-form.jsx b/src/components/search-form.jsx index 180590c..e8d9b1d 100644 --- a/src/components/search-form.jsx +++ b/src/components/search-form.jsx @@ -16,8 +16,11 @@ export function SearchForm({ const { mode, hint } = detectMode(query); const canSearch = mode === "sbd" || mode === "name"; - // Sync from external value (deep-link URL, clear button in parent) + // Mirror the parent's query into local state. The parent owns the value so + // it can bind it to the URL; this only runs when that external value changes + // (deep-link hydration, or the clear button), never in response to typing. useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect setQuery(value); }, [value]);