Skip to content

Commit f01b4b2

Browse files
authored
Merge pull request #87 from galiprandi/quality-usethrottle-improvement-16891675594808657242
✨ Quality: robustness and documentation - useThrottle
2 parents 5bac4d4 + a69658f commit f01b4b2

3 files changed

Lines changed: 30 additions & 1 deletion

File tree

.axioma/quality.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,7 @@
7777
## 2026-06-08 - Distinguishing Abort Reasons in Async Components
7878
**Learning:** When using `AbortController` in components that also implement a `timeOut`, it is critical to distinguish between a manual abort (e.g., due to dependency change or unmount) and a timeout abort. Indiscriminately ignoring all aborted promises in a `.catch` block can swallow legitimate timeout errors.
7979
**Action:** Use `signal.reason` to filter aborts in `.catch` blocks: `if (signal.aborted && signal.reason !== 'Timeout') return`. This ensures race conditions are prevented while still allowing the component to transition to an error state upon timeout.
80+
81+
## 2026-06-10 - [Robust Hook Input Validation]
82+
**Learning:** React hooks that wrap timing-sensitive browser APIs (like `useThrottle`) should implement defensive checks for their numeric parameters (e.g., `limit`). Using a pattern like `if (!limit || limit <= 0)` covers `NaN`, `0`, and negative values, ensuring the hook falls back to an immediate update rather than passing invalid values to `setTimeout`.
83+
**Action:** Always validate numeric inputs in hooks that interact with browser timers or scheduling APIs to provide a safe and predictable fallback.

lib/hooks/useThrottle.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,30 @@ describe('useThrottle', () => {
9494
expect(result.current).toBe('updated 1')
9595
})
9696

97+
it('should return the value immediately when limit is negative', () => {
98+
const { result, rerender } = renderHook(
99+
({ val, limit }) => useThrottle(val, limit),
100+
{
101+
initialProps: { val: 'initial', limit: -100 },
102+
},
103+
)
104+
105+
rerender({ val: 'updated 1', limit: -100 })
106+
expect(result.current).toBe('updated 1')
107+
})
108+
109+
it('should return the value immediately when limit is NaN', () => {
110+
const { result, rerender } = renderHook(
111+
({ val, limit }) => useThrottle(val, limit),
112+
{
113+
initialProps: { val: 'initial', limit: NaN },
114+
},
115+
)
116+
117+
rerender({ val: 'updated 1', limit: NaN })
118+
expect(result.current).toBe('updated 1')
119+
})
120+
97121
it('should use default limit of 500ms when not provided', () => {
98122
const { result, rerender } = renderHook(({ val }) => useThrottle(val), {
99123
initialProps: { val: 'initial' },

lib/hooks/useThrottle.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useEffect, useRef, useState } from 'react'
77
* @template T
88
* @param value - The value to throttle.
99
* @param limit - The limit in milliseconds to throttle the value.
10+
* @default 500
1011
* @returns The throttled value.
1112
*
1213
* @example
@@ -41,7 +42,7 @@ export function useThrottle<T>(value: T, limit: number = 500): T {
4142
return
4243
}
4344

44-
if (limit <= 0) {
45+
if (!limit || limit <= 0) {
4546
setThrottledValue(value)
4647
return
4748
}

0 commit comments

Comments
 (0)