Documentation
Templates
Hooks
useDebounce

useDebounce

Delay value updates to reduce unnecessary operations.

Use Cases

  • Search input that triggers API calls
  • Form validation on input change
  • Window resize handlers
  • Scroll position tracking

Code

import { useState, useEffect } from 'react';
 
export function useDebounce<T>(value: T, delay: number = 300): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);
 
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
 
    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);
 
  return debouncedValue;
}

Usage

Search Input

import { useState } from 'react';
import { useDebounce } from '@/hooks/useDebounce';
import { useQuery } from '@tanstack/react-query';
 
function SearchInput() {
  const [search, setSearch] = useState('');
  const debouncedSearch = useDebounce(search, 300);
 
  const { data, isLoading } = useQuery({
    queryKey: ['search', debouncedSearch],
    queryFn: () => searchAPI(debouncedSearch),
    enabled: debouncedSearch.length > 0,
  });
 
  return (
    <div>
      <input
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        placeholder="Search..."
      />
      {isLoading && <span>Loading...</span>}
      {data?.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}

Form Validation

function EmailInput() {
  const [email, setEmail] = useState('');
  const debouncedEmail = useDebounce(email, 500);
  const [isValid, setIsValid] = useState(true);
 
  useEffect(() => {
    if (debouncedEmail) {
      const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(debouncedEmail);
      setIsValid(valid);
    }
  }, [debouncedEmail]);
 
  return (
    <div>
      <input
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      {!isValid && <span className="text-red-500">Invalid email</span>}
    </div>
  );
}

Variants

useDebounceCallback

For debouncing functions instead of values:

import { useCallback, useRef } from 'react';
 
export function useDebounceCallback<T extends (...args: any[]) => any>(
  callback: T,
  delay: number = 300
): T {
  const timeoutRef = useRef<NodeJS.Timeout>();
 
  return useCallback(
    ((...args) => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
 
      timeoutRef.current = setTimeout(() => {
        callback(...args);
      }, delay);
    }) as T,
    [callback, delay]
  );
}

Usage:

const debouncedSave = useDebounceCallback((value: string) => {
  saveToServer(value);
}, 1000);
 
<input onChange={(e) => debouncedSave(e.target.value)} />