Skip to content

Instantly share code, notes, and snippets.

@thuykaka
Created July 17, 2025 05:02
Show Gist options
  • Select an option

  • Save thuykaka/b255563c45586e082766fcaf728e639f to your computer and use it in GitHub Desktop.

Select an option

Save thuykaka/b255563c45586e082766fcaf728e639f to your computer and use it in GitHub Desktop.
Shadcn InputDebounce
// ref
const inputRef = useRef<HTMLInputElement>(null);
<InputDebounce
ref={inputRef}
onChange={handleSearch}
placeholder="Tìm kiếm..."
delay={300}
/>
// Controlled component
<InputDebounce
value={searchValue}
onChange={setSearchValue}
placeholder="Tìm kiếm..."
/>
// Uncontrolled component
<InputDebounce
defaultValue=""
onChange={handleSearch}
placeholder="Tìm kiếm..."
/>
import * as React from 'react';
/**
* @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx
*/
/**
* A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
* prop or avoid re-executing effects when passed as a dependency
*/
function useCallbackRef<T extends (...args: never[]) => unknown>(
callback: T | undefined
): T {
const callbackRef = React.useRef(callback);
React.useEffect(() => {
callbackRef.current = callback;
});
// https://github.com/facebook/react/issues/19240
return React.useMemo(
() => ((...args) => callbackRef.current?.(...args)) as T,
[]
);
}
export { useCallbackRef };
import * as React from 'react';
import { useCallbackRef } from '@/hooks/use-callback-ref';
export function useDebouncedCallback<T extends (...args: never[]) => unknown>(
callback: T,
delay: number
) {
const handleCallback = useCallbackRef(callback);
const debounceTimerRef = React.useRef(0);
React.useEffect(
() => () => window.clearTimeout(debounceTimerRef.current),
[]
);
const setValue = React.useCallback(
(...args: Parameters<T>) => {
window.clearTimeout(debounceTimerRef.current);
debounceTimerRef.current = window.setTimeout(
() => handleCallback(...args),
delay
);
},
[handleCallback, delay]
);
return setValue;
}
import { useState, ComponentProps, ChangeEvent, memo, useEffect } from 'react';
import { cn } from '@/lib/utils';
import { useDebouncedCallback } from '@/hooks/use-debounced-callback';
import { Input } from '@/component/ui/input';
type InputDebounceProps = {
onChange: (value: string) => void;
delay?: number;
defaultValue?: string;
ref?: React.Ref<HTMLInputElement>;
} & Omit<ComponentProps<'input'>, 'onChange' | 'defaultValue' | 'ref'>;
const InputDebounce = memo(
({
className,
onChange,
delay = 500,
type,
value,
defaultValue,
ref,
...props
}: InputDebounceProps) => {
const isControlled = value !== undefined;
const initialValue = isControlled ? value : (defaultValue ?? '');
const [localValue, setLocalValue] = useState<string>(
initialValue as string
);
useEffect(() => {
if (isControlled) {
setLocalValue(value as string);
}
}, [value, isControlled]);
const debouncedOnChange = useDebouncedCallback((value: string) => {
onChange(value);
}, delay);
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setLocalValue(newValue);
debouncedOnChange(newValue);
};
return (
<Input
ref={ref}
className={cn(className)}
type={type}
{...props}
value={localValue}
onChange={handleInputChange}
/>
);
}
);
InputDebounce.displayName = 'InputDebounce';
export { InputDebounce };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment