Implementar debounce
ou throttle
no React é comum em buscas, inputs e eventos de scroll. Mas testar esse comportamento pode parecer desafiador à primeira vista — especialmente por envolver tempo, timers assíncronos e side effects.
Neste artigo, você vai aprender:
- Diferença entre debounce e throttle
- Como implementá-los no React
- Como testá-los com Jest e @testing-library/react
- Como usar
jest.useFakeTimers()
do jeito certo
🔍 O que é debounce e throttle?
🐢 debounce
Adia a execução até X ms após a última chamada.
🧠 Ideal para: buscas ao digitar, auto-saves, etc.
🐇 throttle
Executa no máximo uma vez a cada X ms, ignorando chamadas subsequentes.
🧠 Ideal para: scroll, resize, cliques rápidos, etc.
🧱 Implementando debounce no React
// SearchInput.tsx
import { useEffect, useState } from 'react';
type Props = {
onSearch: (query: string) => void;
};
export function SearchInput({ onSearch }: Props) {
const [value, setValue] = useState('');
useEffect(() => {
const timeout = setTimeout(() => {
onSearch(value);
}, 500);
return () => clearTimeout(timeout);
}, [value, onSearch]);
return (
<input
placeholder="Search..."
value={value}
onChange={(e) => setValue(e.target.value)}
/>
);
}
🧪 Testando debounce com jest.useFakeTimers()
// SearchInput.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { SearchInput } from './SearchInput';
jest.useFakeTimers();
test('calls onSearch after debounce delay', () => {
const handleSearch = jest.fn();
render(<SearchInput onSearch={handleSearch} />);
const input = screen.getByPlaceholderText(/search/i);
fireEvent.change(input, { target: { value: 'hello' } });
// Ainda não chamou
expect(handleSearch).not.toHaveBeenCalled();
// Avança 500ms
jest.advanceTimersByTime(500);
expect(handleSearch).toHaveBeenCalledWith('hello');
});
⚡ Implementando throttle com lodash
// ThrottledButton.tsx
import { throttle } from 'lodash';
import { useCallback } from 'react';
export function ThrottledButton({ onClick }: { onClick: () => void }) {
const throttled = useCallback(throttle(onClick, 1000), [onClick]);
return <button onClick={throttled}>Click me fast!</button>;
}
🧪 Testando throttle com fake timers
// ThrottledButton.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { ThrottledButton } from './ThrottledButton';
jest.useFakeTimers();
test('calls onClick only once in throttle window', () => {
const handleClick = jest.fn();
render(<ThrottledButton onClick={handleClick} />);
const button = screen.getByText(/click me/i);
fireEvent.click(button);
fireEvent.click(button);
fireEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(1000);
fireEvent.click(button);
expect(handleClick).toHaveBeenCalledTimes(2);
});
🧠 Dicas rápidas
- Use
jest.useFakeTimers()
antes de renderizar o componente. - Sempre finalize com
jest.useRealTimers()
se for fazer outros testes depois. - Debounce pode ser testado com
setTimeout
; throttle exige lib comolodash
oulodash.throttle
.
✅ Conclusão
Testar debounce e throttle é mais fácil do que parece quando você usa as ferramentas certas: fake timers + eventos simulados. Evite confiar apenas no comportamento visual — escrever testes sólidos é o que garante que seu input não dispare chamadas desnecessárias no futuro.
Top comments (0)