Created
July 17, 2025 05:02
-
-
Save thuykaka/b255563c45586e082766fcaf728e639f to your computer and use it in GitHub Desktop.
Shadcn InputDebounce
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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..." | |
| /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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