Skip to content

Refactor components to eliminate unnecessary useEffect hooks and repl…#9

Merged
Yavnik merged 1 commit into
mainfrom
fix/react-hooks-v7-lint
Jun 6, 2026
Merged

Refactor components to eliminate unnecessary useEffect hooks and repl…#9
Yavnik merged 1 commit into
mainfrom
fix/react-hooks-v7-lint

Conversation

@Yavnik

@Yavnik Yavnik commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Removes eslint rule overrides added during the Next.js 16 upgrade and fixes the ~20 flagged sites: effects that only synced derived state are converted to render-time derived-state guards (prevX pattern), mounted flags replaced with useSyncExternalStore via a new useIsMounted hook, write-only guard flags converted to refs, and two genuine external-store sync effects kept with scoped eslint-disable comments.

Summary by CodeRabbit

  • Refactor

    • Improved component state management patterns and synchronization logic across multiple components for better code maintainability.
    • Added new internal utility hook for cross-platform compatibility.
  • Chores

    • Updated ESLint configuration to align with refactored code patterns.

…ace with render-time state adjustments; introduce useIsMounted hook for SSR-safe mounted state management.
Copilot AI review requested due to automatic review settings June 6, 2026 09:38
@Yavnik Yavnik merged commit 804c282 into main Jun 6, 2026
1 check was pending
@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Need an answer fast? Review this PR in Change Stack to ask focused questions about the PR or a changed range.

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3679dac4-1a62-428e-84eb-74934428f9da

📥 Commits

Reviewing files that changed from the base of the PR and between 9ee5505 and 2abe773.

📒 Files selected for processing (15)
  • eslint.config.mjs
  • src/components/archive-mission-dialog.tsx
  • src/components/archive/date-range-selector.tsx
  • src/components/archive/filters/quest-filters.tsx
  • src/components/edit-mission-dialog.tsx
  • src/components/focus-mode/FocusTimer.tsx
  • src/components/focus-mode/InterruptCapture.tsx
  • src/components/live-time.tsx
  • src/components/new-quest-dialog.tsx
  • src/components/quest-board-client.tsx
  • src/components/radar-view-dialog.tsx
  • src/components/theme-selector.tsx
  • src/components/ui/searchable-mission-selector.tsx
  • src/components/welcome-dialog-wrapper.tsx
  • src/hooks/use-is-mounted.ts

📝 Walkthrough

Walkthrough

This PR systematically replaces useEffect-driven state management with render-time state synchronization patterns across multiple components. It introduces a new SSR-safe useIsMounted() hook, removes ESLint rule overrides that are no longer needed, and refactors dialogs, filters, timers, and animations to detect state transitions and perform updates during render instead of in effect callbacks.

Changes

useEffect to render-time synchronization refactor

Layer / File(s) Summary
New useIsMounted() hook and SSR infrastructure
src/hooks/use-is-mounted.ts
Adds useIsMounted() hook implemented via useSyncExternalStore to return false during SSR/hydration and true after client mount, enabling SSR-safe mount detection across components.
ESLint config: remove disabled rules
eslint.config.mjs
Removes react-hooks/set-state-in-effect and react-hooks/immutability rule overrides since patterns that required those disables have been refactored away.
Dialog and form reset patterns
src/components/archive-mission-dialog.tsx, src/components/edit-mission-dialog.tsx, src/components/new-quest-dialog.tsx, src/components/welcome-dialog-wrapper.tsx, src/components/focus-mode/InterruptCapture.tsx
Replaces useEffect-based form resets with render-time state synchronization using prevOpen/prevMissionId/prevInit snapshots; components detect dialog open/close transitions and clear form/error state during render.
Derived state and synchronization via memoization
src/components/archive/date-range-selector.tsx, src/components/archive/filters/quest-filters.tsx
Converts useEffect-based synchronization to useMemo-derived computed values and render-time state snapshots; selectedPreset is derived from range via useMemo, and applied filters sync using prevQuestFilters comparison.
Use useIsMounted hook and useSyncExternalStore for state
src/components/theme-selector.tsx, src/components/live-time.tsx
Replaces useState/useEffect mount detection with useIsMounted() hook; LiveTime also uses useSyncExternalStore for timezone resolution to avoid hydration mismatches.
FocusTimer: complex session and timer state machine
src/components/focus-mode/FocusTimer.tsx
Refactors timer to use useRef guard for one-time session-completion analytics, moves timer configuration reset from dedicated useEffect to render-time prevConfig comparison, and shifts pause/resume tracking to render-time isRunning transition handling.
SearchableMissionSelector: latest-callback refs for error handlers
src/components/ui/searchable-mission-selector.tsx
Adds loadMissionsRef and performSearchRef to capture the latest callback functions, so error toast retry handlers invoke current callbacks instead of stale versions; includes effect to keep refs synchronized.
Animation timing and documentation
src/components/radar-view-dialog.tsx, src/components/quest-board-client.tsx
Radar noise generation moves initial pattern creation to first interval tick instead of synchronous setState in effect; quest-board-client adds inline documentation and targeted ESLint rule disable/enable around intentional external-store updates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Yavnik/commandops#8: Both PRs modify eslint.config.mjs's handling of the react-hooks/set-state-in-effect rule, with this PR removing those overrides as patterns are refactored away.

Poem

🐰 No more effects in the shadows,
State flows fresh at render time,
Snapshots catch each change with grace,
Hooks and refs in perfect rhyme,
useSyncExternalStore's a keeper too!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/react-hooks-v7-lint

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes temporary ESLint rule overrides added during the Next.js 16 upgrade by refactoring ~20 hook sites: effect-only derived state is moved to render-time guarded updates, mounted flags are replaced with a new useIsMounted() hook based on useSyncExternalStore, and the remaining legitimate external-store sync effects keep narrowly-scoped ESLint disables.

Changes:

  • Add useIsMounted() (SSR/hydration-safe) and migrate mounted-guard patterns to it.
  • Refactor multiple components to remove “sync derived state in useEffect” patterns (using guarded render-time updates, refs, or memoized derivations).
  • Remove global ESLint rule overrides and keep only scoped disables where effects are truly required.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/hooks/use-is-mounted.ts New useSyncExternalStore-based mounted hook for SSR/hydration safety.
src/components/welcome-dialog-wrapper.tsx Opens dialog based on showHelp via guarded render-time update; keeps effect for URL cleanup.
src/components/ui/searchable-mission-selector.tsx Converts retry handlers to use latest-callback refs; scopes effect lint disables for data fetching.
src/components/theme-selector.tsx Replaces mounted state/effect with useIsMounted().
src/components/radar-view-dialog.tsx Avoids immediate effect-body state set by generating noise pattern on first interval tick.
src/components/quest-board-client.tsx Documents and scopes ESLint disable for intentional external-store synchronization effect.
src/components/new-quest-dialog.tsx Replaces form init and default-deadline effects with guarded render-time updates.
src/components/live-time.tsx Replaces mounted + timezone effects with useIsMounted() + useSyncExternalStore snapshot.
src/components/focus-mode/InterruptCapture.tsx Moves modal-open reset logic to guarded render-time update; effect handles focus + timer with cleanup.
src/components/focus-mode/FocusTimer.tsx Refactors timer resets and guards to avoid effect-only derived state; uses refs for write-only guard.
src/components/edit-mission-dialog.tsx Moves mission->form synchronization to guarded render-time update.
src/components/archive/filters/quest-filters.tsx Moves external reset sync to guarded render-time update.
src/components/archive/date-range-selector.tsx Derives selected preset via memo; changes range->tempRange syncing logic.
src/components/archive-mission-dialog.tsx Moves open/close reset logic to guarded render-time update.
eslint.config.mjs Removes global disables for react-hooks/set-state-in-effect and react-hooks/immutability.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +60 to +61
const loadMissionsRef = useRef<() => void>(undefined);
const performSearchRef = useRef<(query: string) => void>(undefined);
Comment on lines +104 to 108
// Sync tempRange when the incoming range changes (render-time).
const [prevRange, setPrevRange] = useState(range);
if (range !== prevRange) {
setPrevRange(range);
setTempRange(range);
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