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
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]);
}
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"
/>
);
}
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)