Skip to content

refactor: remove the set-state-in-effect suppressions#117

Merged
aaronjmars merged 1 commit into
mainfrom
refactor/remove-set-state-in-effect-suppressions
Jul 20, 2026
Merged

refactor: remove the set-state-in-effect suppressions#117
aaronjmars merged 1 commit into
mainfrom
refactor/remove-set-state-in-effect-suppressions

Conversation

@aaronjmars

Copy link
Copy Markdown
Collaborator

Follow-up to #116, which disabled three react-hooks/set-state-in-effect reports with rationale rather than fixing them. Each had a real fix that also removes state.

ColumnCard search row

Two effects existed only to open the row. Opening is a state adjustment, so it now happens during render — React re-runs the component immediately, before paint, with no cascading commit.

The query-driven open has to stay edge-triggered, and this is the part worth reviewing. The obvious version —

if (searchActive && !searchOpen) setSearchOpen(true);

— is wrong. Both the "Esc" button and the toolbar toggle close the row without clearing the query, so that test re-opens it on the very next render and the row becomes impossible to dismiss. I wrote that version first and caught it reading the close handlers. Comparing against the previous value keeps it a transition, which is what the old [searchActive] dep array was really encoding.

The mount case (a query that survived a collapse/expand or tab switch) moves to seeding searchOpen from searchActive, which also removes a one-frame flicker. The effect is left with the two jobs that genuinely belong in one: moving DOM focus and draining the one-shot / signal.

VersionHistoryDialog

The dialog stays mounted for the lifetime of the sidebar — that was the only reason it had to set a loading flag synchronously on open. Splitting the list into a child that mounts per-open and is keyed by deck makes useState's initial value the reset.

snapshots: DeckSnapshotMeta[] | null then encodes "not loaded yet" directly, so the separate loading boolean disappears — along with a one-render flash of the empty-state copy that the old loading: false default allowed on first open.

Verification

There is no test coverage for any of this (the 279 tests don't touch either file), and reasoning alone already produced one bug here, so I exercised it against a live deck in a browser:

Scenario Result
Close search via "Esc" with active query stays closed ✅
Query survives close → reopen nvidia retained ✅
Clear query mid-type row stays open ✅
/ shortcut opens and focuses input ✅
Version history open snapshots load ✅
Close → reopen dialog fresh load, no stale state ✅

Console: 0 React errors, 0 warnings — no "cannot update while rendering", no render loop.

eslint --max-warnings=0 clean · 279/279 tests · tsc --noEmit · next build all pass. No set-state-in-effect disables remain in the codebase.

All three were disabled with rationale rather than fixed. Each had a real
fix that also simplifies the code.

ColumnCard opened its search row from two effects. Opening is a state
adjustment, so it now happens during render — React re-runs the component
before paint, with no cascading commit and no frame where the row is
missing.

The query-driven open has to stay edge-triggered. Both the "Esc" button
and the toolbar toggle close the row without clearing the query, so a
plain `searchActive && !searchOpen` test re-opens it on the next render
and makes the row impossible to dismiss. Comparing against the previous
value keeps it a transition; the mount case is covered by seeding
`searchOpen` from `searchActive` instead. The effect keeps the two jobs
that belong in one: moving focus and draining the one-shot signal.

VersionHistoryDialog stays mounted for the lifetime of the sidebar, which
is the only reason it needed to set a loading flag synchronously on open.
Split the list into a child mounted per-open and keyed by deck, so
`useState`'s initial value is the reset. `snapshots: null` then encodes
"not loaded yet" and the separate `loading` boolean goes away, along with
a one-render flash of the empty-state copy that the old `loading: false`
default allowed.

Verified against a live deck: closing search with an active query stays
closed, the query survives close/reopen, clearing it mid-type keeps the
row up, `/` opens and focuses, and the dialog reloads cleanly on reopen.
No React warnings in the console.
@aaronjmars
aaronjmars merged commit 3979ee3 into main Jul 20, 2026
2 checks passed
@aaronjmars
aaronjmars deleted the refactor/remove-set-state-in-effect-suppressions branch July 20, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant