From 406a1c2d1ca17a7ea5bf0d81034dbbf62acd7f48 Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Fri, 31 Oct 2025 10:47:15 +0530 Subject: [PATCH 1/3] feat: add useDebouncedState hook --- packages/raystack/hooks/index.tsx | 1 + packages/raystack/hooks/useDebouncedState.tsx | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 packages/raystack/hooks/useDebouncedState.tsx diff --git a/packages/raystack/hooks/index.tsx b/packages/raystack/hooks/index.tsx index 8b3da246..580ae6cc 100644 --- a/packages/raystack/hooks/index.tsx +++ b/packages/raystack/hooks/index.tsx @@ -1,3 +1,4 @@ export { useCopyToClipboard } from './useCopyToClipboard'; export { useMouse } from './useMouse'; export { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; +export { useDebouncedState } from './useDebouncedState'; diff --git a/packages/raystack/hooks/useDebouncedState.tsx b/packages/raystack/hooks/useDebouncedState.tsx new file mode 100644 index 00000000..c79ea514 --- /dev/null +++ b/packages/raystack/hooks/useDebouncedState.tsx @@ -0,0 +1,63 @@ +import { + SetStateAction, + useCallback, + useEffect, + useRef, + useState +} from 'react'; + +export interface UseDebouncedStateOptions { + leading?: boolean; +} + +export type UseDebouncedStateReturnValue = [ + T, + (newValue: SetStateAction) => void +]; + +/** + * A hook that debounces the state update. + * @param defaultValue - The default value of the state. + * @param wait - The wait time in milliseconds. + * @param options - The options for the hook. + * @returns A tuple containing the current value and the debounced set value function. + * + * @example + * const [value, setValue] = useDebouncedState('Hello', 1000); + + * @example + * const [value, setValue] = useDebouncedState('Hello', 1000, { leading: true }); + */ +export function useDebouncedState( + defaultValue: T, + wait: number, + options: UseDebouncedStateOptions = { leading: false } +): UseDebouncedStateReturnValue { + const [value, setValue] = useState(defaultValue); + const timeoutRef = useRef(null); + const leadingRef = useRef(true); + + const clearTimeout = useCallback( + () => window.clearTimeout(timeoutRef.current!), + [] + ); + useEffect(() => clearTimeout, [clearTimeout]); + + const debouncedSetValue = useCallback( + (newValue: SetStateAction) => { + clearTimeout(); + if (leadingRef.current && options.leading) { + setValue(newValue); + } else { + timeoutRef.current = window.setTimeout(() => { + leadingRef.current = true; + setValue(newValue); + }, wait); + } + leadingRef.current = false; + }, + [options.leading, clearTimeout, wait] + ); + + return [value, debouncedSetValue] as const; +} From df81a402885f5a75b4f114fb72bb08a530c7d765 Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Fri, 31 Oct 2025 11:49:59 +0530 Subject: [PATCH 2/3] chore: update variable name in useDebouncedState --- packages/raystack/hooks/useDebouncedState.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/raystack/hooks/useDebouncedState.tsx b/packages/raystack/hooks/useDebouncedState.tsx index c79ea514..f0f0bad1 100644 --- a/packages/raystack/hooks/useDebouncedState.tsx +++ b/packages/raystack/hooks/useDebouncedState.tsx @@ -18,7 +18,7 @@ export type UseDebouncedStateReturnValue = [ /** * A hook that debounces the state update. * @param defaultValue - The default value of the state. - * @param wait - The wait time in milliseconds. + * @param delay - The delay time in milliseconds. * @param options - The options for the hook. * @returns A tuple containing the current value and the debounced set value function. * @@ -30,7 +30,7 @@ export type UseDebouncedStateReturnValue = [ */ export function useDebouncedState( defaultValue: T, - wait: number, + delay: number, options: UseDebouncedStateOptions = { leading: false } ): UseDebouncedStateReturnValue { const [value, setValue] = useState(defaultValue); @@ -52,11 +52,11 @@ export function useDebouncedState( timeoutRef.current = window.setTimeout(() => { leadingRef.current = true; setValue(newValue); - }, wait); + }, delay); } leadingRef.current = false; }, - [options.leading, clearTimeout, wait] + [options.leading, clearTimeout, delay] ); return [value, debouncedSetValue] as const; From 3dc66c8bda5206991dbb5f07cbc12cca49c13dc3 Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Fri, 31 Oct 2025 16:00:38 +0530 Subject: [PATCH 3/3] feat: add useDebouncedState missing description --- packages/raystack/hooks/useDebouncedState.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/raystack/hooks/useDebouncedState.tsx b/packages/raystack/hooks/useDebouncedState.tsx index f0f0bad1..125119b0 100644 --- a/packages/raystack/hooks/useDebouncedState.tsx +++ b/packages/raystack/hooks/useDebouncedState.tsx @@ -7,6 +7,11 @@ import { } from 'react'; export interface UseDebouncedStateOptions { + /** + * If `true`, the state will be updated immediately on the first call (leading edge). + * Subsequent calls within the delay period will be debounced. + * @default false + */ leading?: boolean; }