Cristian Gatu

Caută sau rulează o comandă...

Caută o pagină sau rulează o comandă

Înapoi la articole

Cum implementezi debounce, în varianta simplă

23 septembrie 2026 · 3 minute de citit

Debounce apare mereu în aceleași câteva locuri: un câmp de căutare care nu trebuie să facă un request la fiecare apăsare de tastă, un handler de resize care nu trebuie să ruleze la fiecare pixel, un formular salvat-pe-măsură-ce-scrii care nu trebuie să lovească rețeaua la fiecare caracter. Conceptul e simplu — aștepți o pauză înainte să faci lucrul respectiv — iar implementarea e mai simplă decât se așteaptă majoritatea.

Varianta în JavaScript simplu

Uită de React pentru o secundă. Un debounce e o funcție care înfășoară altă funcție și îi întârzie execuția, resetând întârzierea de fiecare dată când e apelată din nou înainte să se declanșeze.

function debounce<Args extends unknown[]>(
  fn: (...args: Args) => void,
  delayMs: number
) {
  let timeoutId: ReturnType<typeof setTimeout>;

  return (...args: Args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delayMs);
  };
}

Asta e tot. Fiecare apel anulează timeout-ul în așteptare și programează unul nou; funcția înfășurată rulează abia când apelurile se opresc pentru delayMs.

Adaptat pentru React: o valoare debounced

Într-o componentă, de obicei nu ai nevoie de o funcție debounced, ci de o valoare debounced, ca restul componentei s-o poată citi normal, ca pe orice state.

function useDebouncedValue<T>(value: T, delayMs: number): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timeoutId = setTimeout(() => setDebounced(value), delayMs);
    return () => clearTimeout(timeoutId);
  }, [value, delayMs]);

  return debounced;
}

Folosirea e o singură linie:

const [query, setQuery] = useState("");
const debouncedQuery = useDebouncedValue(query, 300);

useEffect(() => {
  if (!debouncedQuery) return;
  fetchResults(debouncedQuery);
}, [debouncedQuery]);

Input-ul se actualizează imediat — fără lag în timp ce scrii — iar debouncedQuery se aliniază abia la 300ms după ce te oprești din scris. Funcția de cleanup e cea care face asta corect: fiecare tastă apăsată șterge timeout-ul anterior înainte să programeze unul nou, deci din tot ce ai scris rapid, doar ultimul apel se declanșează vreodată.

Când merită mai mult de atât

Asta acoperă marea majoritate a cazurilor reale. Apelează la o librărie (use-debounce, lodash.debounce) când ai nevoie de lucruri pe care hook-ul ăsta nu le face din start: un apel leading-edge (declanșare imediată, apoi ignorarea rafalei), un plafon maxWait ca o valoare care nu se oprește niciodată din schimbat să tot fie actualizată din când în când, sau debounce pe un callback ref în loc de o valoare. Până când chiar ai nevoie de așa ceva, cele zece linii de mai sus înseamnă o dependență mai puțin de instalat și un API mai puțin de învățat.