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
Risk
High. These changes touch how state flows in many components. Strongly suggest landing in small per-area PRs and watching for behavior diffs.
Parent tracking issue: #1455
Summary
Resolve:
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:
Approach
Scope
Production code (`client/src/`). Affects pages, hooks, and shared components broadly.
Acceptance Criteria
Risk
High. These changes touch how state flows in many components. Strongly suggest landing in small per-area PRs and watching for behavior diffs.