DEV Community

Cover image for Closure em React na prática: criando um useDebounce em 15 linhas
CarlosRogerio Orioli
CarlosRogerio Orioli

Posted on • Edited 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)