Parent tracking issue: #1455
Summary
Resolve:
- 8 `@eslint-react/web-api-no-leaked-timeout` warnings
- 2 `@eslint-react/web-api-no-leaked-fetch` warnings
Both flag side effects started in `useEffect` that aren't cleaned up on unmount — leaked timers fire after the component is gone, and in-flight fetches may set state on unmounted components.
Fix patterns
```tsx
// timeout — before
useEffect(() => {
setTimeout(() => doSomething(), 1000);
}, []);
// timeout — after
useEffect(() => {
const id = setTimeout(() => doSomething(), 1000);
return () => clearTimeout(id);
}, []);
// fetch — before
useEffect(() => {
fetch('/api/foo').then((r) => r.json()).then(setData);
}, []);
// fetch — after
useEffect(() => {
const controller = new AbortController();
fetch('/api/foo', { signal: controller.signal })
.then((r) => r.json())
.then(setData)
.catch((e) => { if (e.name !== 'AbortError') throw e; });
return () => controller.abort();
}, []);
```
Scope
Production code (`client/src/`).
Acceptance Criteria
Risk
Low. Standard cleanup pattern, mechanical to apply.
Parent tracking issue: #1455
Summary
Resolve:
Both flag side effects started in `useEffect` that aren't cleaned up on unmount — leaked timers fire after the component is gone, and in-flight fetches may set state on unmounted components.
Fix patterns
```tsx
// timeout — before
useEffect(() => {
setTimeout(() => doSomething(), 1000);
}, []);
// timeout — after
useEffect(() => {
const id = setTimeout(() => doSomething(), 1000);
return () => clearTimeout(id);
}, []);
// fetch — before
useEffect(() => {
fetch('/api/foo').then((r) => r.json()).then(setData);
}, []);
// fetch — after
useEffect(() => {
const controller = new AbortController();
fetch('/api/foo', { signal: controller.signal })
.then((r) => r.json())
.then(setData)
.catch((e) => { if (e.name !== 'AbortError') throw e; });
return () => controller.abort();
}, []);
```
Scope
Production code (`client/src/`).
Acceptance Criteria
Risk
Low. Standard cleanup pattern, mechanical to apply.