Skip to content

fix(react): correct effect dependency lifecycles - #884

Merged
Neonforge98 merged 1 commit into
developfrom
codex/react-effect-dependencies
Aug 22, 2026
Merged

fix(react): correct effect dependency lifecycles#884
Neonforge98 merged 1 commit into
developfrom
codex/react-effect-dependencies

Conversation

@Harry19081

Copy link
Copy Markdown
Member

Problem

Inline suppressions hid 86 react-hooks/exhaustive-deps diagnostics across the audited React surface. In this category's files, several were genuine lifecycle bugs: WebSocket option changes were ignored, asynchronous conversation and branch fetches captured stale snapshots, listeners/callbacks could keep old props, and mount-only effects mixed true ownership constraints with accidental omissions.

This PR owns 67 active diagnostics plus 3 stale directives in its non-overlapping file set. Companion low-risk and state-sync PRs own the remaining 19 active diagnostics.

Solution

  • Fixed 39 active dependency diagnostics with lifecycle-correct dependencies, primitive option keys, current-store reads for feedback-prone atoms, and event-time callback refs / useEffectEvent where resubscription would be wrong.
  • Removed 3 stale, unused directives.
  • Retained 26 intentional directives (28 diagnostics: two caller-dependency hooks each emit two) and added a precise -- reason to every one.
  • Kept xterm/CodeMirror ownership stable: callback, content, theme, and font changes continue to patch existing instances instead of destroying renderers or WebGL slots.
  • Added WSProvider lifecycle regression coverage: equivalent option objects do not reconnect, real primitive changes do, and undefined options no longer overwrite client defaults.
  • Documented the newly exposed set-state-in-effect cases narrowly: WSProvider publishes an externally owned disposable client, and the realtime capability probe clears state before its async result.

Potential risks

  • WSProvider now intentionally recreates its client when an actual option primitive or onBeforeReconnect changes. Parents that change callback identity every render should stabilize that callback.
  • Conversation/branch fetch effects now read the latest Jotai snapshot at trigger time. Existing in-flight and cancellation guards remain responsible for late results.
  • The retained semantic keys are lifecycle contracts. If a projection begins reading another field, its key must be expanded with it.

Retained exhaustive-deps suppressions

  • Mount / identity ownership: ComposerInput/index, InlineRenameInput, VirtualizedListBase, XtermOutput, PokerTableWindow.
  • Editor instance ownership: both CodeMirror/Diff construction effects.
  • Semantic content / invalidation keys: CloudSessionReferencePreview, CloudSessionShareDialog, both resolved-model label hooks, three FileSidebar projections, ShellSidebar, useSubmissionsData, useTeamRuntimeRoster, useFileMode, and three NavigationMenu hooks.
  • Caller / external clocks: useCloudOrgPanelState, useEventStoreSelector, useAsyncData, useCallbackRefEffect, and useScrollToBottom.

Verification

  • pnpm exec eslint $(git diff --name-only origin/develop...HEAD --diff-filter=ACM | rg '\.(ts|tsx)$') --report-unused-disable-directives
  • rg -l 'eslint-(disable|disable-next-line|disable-line).*react-hooks/exhaustive-deps' src | xargs pnpm exec eslint --no-inline-config --rule 'react-hooks/exhaustive-deps: warn' --format json — 47 visible diagnostics: 28 intentional/reasoned here, 19 assigned to companion PRs.
  • pnpm typecheck
  • Focused Vitest run: 25 files / 203 tests passed across ComposerInput, TerminalInteractive, embedded webview, deep links, navigation selection, Org2 replay, and shell replay.
  • pnpm exec vitest run src/api/realtime/websocket/WSProvider.test.ts — 2 tests passed.
  • git diff --check origin/develop...HEAD
  • Commit hooks: lint-staged and scoped TypeScript checks passed for all 48 files.

Performance lifecycle evidence

Lifecycle Evidence Churn outcome
WebSocket client Dependencies are option primitives, not the options object; regression test covers equal-object and changed-value renders. No reconnect for equivalent option allocations; one rebuild for a real option change.
Terminal / xterm Event callbacks read current refs; appearance effects patch the existing instance; shell integration only changes the mount key when availability changes. No callback/theme/content-driven renderer rebuild or extra WebGL slot.
Conversation and branch fetches Trigger effects read current atom snapshots instead of depending on atoms they update. Avoids self-triggered fetch loops while preserving signal/repo clocks and cleanup guards.
Listeners and timers Stable callbacks are included; every subscription still returns its original cleanup; debounce/scroll caller clocks remain explicit. No added listener/timer ownership or resubscribe loop.
Semantic projections Content keys were retained and strengthened where live status fields were missing. Equivalent arrays preserve memo identity; real rendered-field changes invalidate.

Performance verdict: pass for lifecycle/churn review and focused automated coverage. No live Tauri profiling claim is made in this PR.

@Neonforge98
Neonforge98 merged commit 33f615c into develop Aug 22, 2026
3 checks passed
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.

2 participants