Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/raystack/hooks/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { useCopyToClipboard } from './useCopyToClipboard';
export { useMouse } from './useMouse';
export { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
export { useDebouncedState } from './useDebouncedState';
68 changes: 68 additions & 0 deletions packages/raystack/hooks/useDebouncedState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {
SetStateAction,
useCallback,
useEffect,
useRef,
useState
} 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;
}

export type UseDebouncedStateReturnValue<T> = [
T,
(newValue: SetStateAction<T>) => void
];

/**
* A hook that debounces the state update.
* @param defaultValue - The default value of the state.
* @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.
*
* @example
* const [value, setValue] = useDebouncedState('Hello', 1000);

* @example
* const [value, setValue] = useDebouncedState('Hello', 1000, { leading: true });
*/
export function useDebouncedState<T = any>(
defaultValue: T,
delay: number,
options: UseDebouncedStateOptions = { leading: false }
): UseDebouncedStateReturnValue<T> {
const [value, setValue] = useState(defaultValue);
const timeoutRef = useRef<number | null>(null);
const leadingRef = useRef(true);

const clearTimeout = useCallback(
() => window.clearTimeout(timeoutRef.current!),
[]
);
useEffect(() => clearTimeout, [clearTimeout]);

const debouncedSetValue = useCallback(
(newValue: SetStateAction<T>) => {
clearTimeout();
if (leadingRef.current && options.leading) {
setValue(newValue);
} else {
timeoutRef.current = window.setTimeout(() => {
leadingRef.current = true;
setValue(newValue);
}, delay);
}
leadingRef.current = false;
},
[options.leading, clearTimeout, delay]
);

return [value, debouncedSetValue] as const;
}