DEV Community

Rogerio Orioli
Rogerio Orioli

Posted on

Closure em React na prática: criando um useDebounce em 15 linhas

Você já percebeu que algumas funções “lembram” valores mesmo depois que o componente re-renderiza? Isso acontece por causa de closures.

O que é uma closure?

Em JavaScript, uma closure é quando uma função consegue acessar variáveis do escopo onde foi criada, mesmo depois desse escopo ter sido executado.
Ou seja, a função “fecha” (close over) sobre o ambiente original, preservando valores para usos futuros.

Exemplo simples em JS puro:

function contador() {
  let count = 0;
  return function() {
    count++;
    return count;
  };
}

const incrementar = contador();
console.log(incrementar()); // 1
console.log(incrementar()); // 2
console.log(incrementar()); // 3
Enter fullscreen mode Exit fullscreen mode

A função incrementar mantém acesso à variável count, mesmo depois que contador() já terminou de executar. Isso é uma closure em ação.

O problema no React

Ao digitar num input e disparar uma busca a cada tecla, sua API sofre. Queremos esperar o usuário parar de digitar por alguns milissegundos antes de buscar.

A solução com closure

Vamos criar um hook useDebounce que devolve uma função “debounced”. Essa função usa uma variável do escopo (timerRef) que fica preservada graças à closure.

import { useRef, useCallback } from "react";

export function useDebounce(fn, delay = 400) {
  const timerRef = useRef(null);

  return useCallback((...args) => {
    if (timerRef.current) window.clearTimeout(timerRef.current);
    // 'fn' e 'timerRef' ficam acessíveis aqui graças à closure
    timerRef.current = window.setTimeout(() => {
      fn(...args);
    }, delay);
  }, [fn, delay]);
}

Enter fullscreen mode Exit fullscreen mode

Por que isso é uma closure?

A função retornada por useCallback captura (close over) timerRef, fn e delay.
Mesmo que o componente re-renderize, a função ainda consegue acessar e atualizar timerRef corretamente.

Usando no componente

import { useState, useCallback } from "react";
import { useDebounce } from "./useDebounce";

export default function SearchBox() {
  const [query, setQuery] = useState("");

  const fetchResults = useCallback((q) => {
    console.log("Buscando por:", q);
  }, []);

  const debouncedFetch = useDebounce(fetchResults, 600);

  return (
    <input
      value={query}
      onChange={(e) => {
        const q = e.target.value;
        setQuery(q);
        debouncedFetch(q);
      }}
      placeholder="Busque produtos..."
      className="border rounded-lg p-2 w-full"
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Armadilha comum: “stale closure”

Se fetchResults depender de algum estado que muda, você precisa incluir esse estado nas dependências do useCallback. Caso contrário, a closure pode capturar valores antigos.

Resumo:

Closure = função que lembra variáveis do escopo onde foi criada.

Em React, closures permitem que funções de hooks e handlers preservem contexto entre renders.

Nosso useDebounce funciona porque a função debounced mantém acesso ao timerRef.

Top comments (0)