Summary
useTimeout initializes its ref to the literal -1, typed as ReturnType<typeof setTimeout> | number. clearTimeout(-1) is a no-op in browsers, but it sends an invalid handle to the WHATWG timers API.
Root cause
```ts
// src/hooks/useTimeout.ts:4
const ref = useRef<ReturnType | number>(-1)
```
The sentinel value -1 is used to indicate "no active timer", but the cleanup branch calls clearTimeout(ref.current as ReturnType<typeof setTimeout>) unconditionally.
Impact
Minor — works in practice in every JS runtime, but:
- Technically out-of-contract (browsers don't guarantee
-1 is safe forever).
- Sloppy type assertion
as ReturnType<typeof setTimeout> hides the issue.
Expected behavior
Use null as the sentinel and guard the cleanup:
```ts
const ref = useRef<ReturnType | null>(null)
const stop = useCallback(() => {
if (ref.current !== null) {
clearTimeout(ref.current)
ref.current = null
}
}, [])
```
Summary
useTimeoutinitializes its ref to the literal-1, typed asReturnType<typeof setTimeout> | number.clearTimeout(-1)is a no-op in browsers, but it sends an invalid handle to the WHATWG timers API.Root cause
```ts
// src/hooks/useTimeout.ts:4
const ref = useRef<ReturnType | number>(-1)
```
The sentinel value
-1is used to indicate "no active timer", but the cleanup branch callsclearTimeout(ref.current as ReturnType<typeof setTimeout>)unconditionally.Impact
Minor — works in practice in every JS runtime, but:
-1is safe forever).as ReturnType<typeof setTimeout>hides the issue.Expected behavior
Use
nullas the sentinel and guard the cleanup:```ts
const ref = useRef<ReturnType | null>(null)
const stop = useCallback(() => {
if (ref.current !== null) {
clearTimeout(ref.current)
ref.current = null
}
}, [])
```