(value: T, debounceTimeoutMs: number)
| 85 | * Takes any value, and returns out a debounced version of it. |
| 86 | */ |
| 87 | export function useDebouncedValue<T>(value: T, debounceTimeoutMs: number): T { |
| 88 | if (!Number.isInteger(debounceTimeoutMs) || debounceTimeoutMs < 0) { |
| 89 | throw new Error( |
| 90 | `Invalid value ${debounceTimeoutMs} for debounceTimeoutMs. Value must be an integer greater than or equal to zero.`, |
| 91 | ); |
| 92 | } |
| 93 | |
| 94 | const [debouncedValue, setDebouncedValue] = useState(value); |
| 95 | |
| 96 | // If the debounce timeout is ever zero, synchronously flush any state syncs. |
| 97 | // Doing this mid-render instead of in useEffect means that we drastically cut |
| 98 | // down on needless re-renders, and we also avoid going through the event loop |
| 99 | // to do a state sync that is *intended* to happen immediately |
| 100 | if (value !== debouncedValue && debounceTimeoutMs === 0) { |
| 101 | setDebouncedValue(value); |
| 102 | } |
| 103 | useEffect(() => { |
| 104 | if (debounceTimeoutMs === 0) { |
| 105 | return; |
| 106 | } |
| 107 | |
| 108 | const timeoutId = setTimeout(() => { |
| 109 | setDebouncedValue(value); |
| 110 | }, debounceTimeoutMs); |
| 111 | return () => clearTimeout(timeoutId); |
| 112 | }, [value, debounceTimeoutMs]); |
| 113 | |
| 114 | return debouncedValue; |
| 115 | } |
no outgoing calls