Skip to content

chore: set-state-in-effect (76) + exhaustive-deps (74) refactors #1469

@steilerDev

Description

@steilerDev

Parent tracking issue: #1455

Summary

Resolve:

  • 76 `@eslint-react/set-state-in-effect` warnings
  • 74 `@eslint-react/exhaustive-deps` warnings

This is the largest semantic refactor in the cleanup. Unlike the other issues, this one requires careful judgement on each case rather than a mechanical rewrite.

Why these need judgement

set-state-in-effect

Calling `setState` inside `useEffect` often indicates derivable state that should be computed during render instead:

```tsx
// before — extra render + risk of stale state
const [filtered, setFiltered] = useState([]);
useEffect(() => {
setFiltered(items.filter((i) => i.active));
}, [items]);

// after — derivation in render, no extra cycle
const filtered = useMemo(() => items.filter((i) => i.active), [items]);
```

But not all instances are derivable — sometimes the state genuinely needs to live as state (e.g., user-edited copy of server data). For those, an inline disable with a reason is appropriate.

exhaustive-deps

The rule wants every value referenced inside the effect/memo/callback to appear in the deps array. Common gotchas:

  • Adding a function or object dep that's recreated every render → infinite loop. Fix by stabilizing the dep (`useCallback`/`useMemo`) or moving the function inside the effect.
  • Adding a deliberately-stale closure capture → may need a ref instead.
  • Suppressing with `// eslint-disable-next-line` is sometimes the right call — document why on the disable line.

Approach

  • Split into multiple PRs by component area. Don't bundle these — each requires reading and understanding the component's state model.
  • Best done by someone with knowledge of the affected components. The hand-off cost on unfamiliar code is high.
  • Prefer derivation (`useMemo`/render-time compute) over `setState` in effects.
  • When suppressing `exhaustive-deps`, always include a reason comment.

Scope

Production code (`client/src/`). Affects pages, hooks, and shared components broadly.

Acceptance Criteria

  • `npm run lint` reports 0 `@eslint-react/set-state-in-effect` warnings (or all remaining have justified inline disables)
  • `npm run lint` reports 0 `@eslint-react/exhaustive-deps` warnings (or all remaining have justified inline disables)
  • No regressions in component behavior — verified by existing unit/E2E coverage
  • No new infinite render loops (manual check during development)
  • PR description explains any non-trivial state-model changes

Risk

High. These changes touch how state flows in many components. Strongly suggest landing in small per-area PRs and watching for behavior diffs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    tech-debtTechnical debt cleanup work (lint, refactors, etc.)

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions