style(frontend): document the four one-shot setState-in-effect sites

eslint-plugin-react-hooks v7 flags setState inside an effect body. All four
occurrences predate this refactor and are one-shot initialisation or external
sync, not the cascading-render pattern the rule targets:

  - hydrating a ?q= deep link once the database is ready
  - reading the candidate count for the footer
  - auto-running the schema preset when the SQL tab first opens
  - mirroring the parent-owned query into SearchForm for deep links and clear

Each gets a scoped disable with a comment explaining why it is correct here,
rather than a blanket rule change. Restructuring these properly would mean
reworking deep-link hydration and auto-search with no test harness to catch a
regression; that is worth doing separately, not inside this refactor.

npm run lint is now clean.
This commit is contained in:
2026-08-13 11:48:31 +07:00
parent 83bbc597ce
commit 003e7c8afd
3 changed files with 11 additions and 3 deletions
+6 -2
View File
@@ -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]);
+1
View File
@@ -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);
}
+4 -1
View File
@@ -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]);