Otimização de buscas aplicando debounce em inputs
Reduza a carga de trabalho na sua aplicação e melhore a experiência dos usuários.
Provavelmente você já deve ter se deparado com inputs de buscas como o do Twitter ou Spotify: você digita sua pesquisa e, após um curto período, a aplicação retorna os resultados. Esse comportamento, conhecido como debounce
, auxilia as aplicações a reduzirem a sobrecarga no front e back-end, uma vez que o front-end limita a quantidade de execução de funções ou mesmo requisições ao servidor a cada tecla digitada. De modo geral, o front-end observa se há um intervalo, como por exemplo 500ms
, sem nenhuma tecla ser digitada, e só então executa uma função como uma chamada à API. Dessa forma, você pode aumentar a performance da sua aplicação, principalmente em cenários com muitos usuários utilizando simultaneamente.
Um pouco de contexto
Apesar do debounce ser comumente implementado em inputs de buscas e filtros, o termo tem sua origem na eletrônica. Com um comportamento fundamentalmente similar ao aplicado no desenvolvimento de software, o debounce surgiu como uma solução para o comportamento indesejado de múltiplos cliques de um botão. Por exemplo, ao apertar o botão de um dispositivo eletrônico, como um controle de video game, o sinal é processado diversas de vezes antes que o usuário consiga parar de pressionar o botão, seja por conta de imperfeições na placa de circuito eletrônico ou mesmo pela velocidade dos microprocessadores.
Exemplo de input com debounce
Nenhuma explicação será tão eficaz quando a experiência, então deixo abaixo um exemplo comparativo entre um input comum e um input com debounce. Caso queria ver o código fonte, ele está disponível nesse repositório, mas, de ante mão adianto que toda a lógica utilizada nele será abordada ao longo desse artigo.
Implementando debounce com JavaScript
Para iniciar, vamos trabalhar com JavaScript puro utilizando a API do DOM e exibir o resultado no console
do navegador. Para isso precisamos ter acesso ao nosso input e criar uma função para lidar com o evento keyup
:
Após isso é necessário adicionar um delay à nossa função, dessa forma a função para lidar com esse evento só será executada após um intervalo de tempo:
No entanto, perceba que isso gerou um bug, pois ao passar o intervalo de 500ms
o texto do input é exibido diversas vezes no console. Para corrigir esse problema, é necessário cancelar o timeout
criado anteriormente, caso uma nova tecla seja pressionada. Para isso, podemos utilizar o timeoutID
retornado pela função setTimeout()
e passá-lo como parâmetro na função clearTimeout()
, da forma recomendada pela documentação da MDN:
Observe que a variável timeoutId
foi mantida fora da função handleKeyUp()
para que seu valor seja preservado mesmo que a função seja chamada mais de uma vez, como no caso de novas teclas sendo pressionadas.
Dessa forma, terminamos de implementar o debounce no nosso input. Contudo, não conseguimos reutilizar o mesmo comportamento em outros contextos uma vez que ele está acoplado à lógica da função handleKeyUp()
. Sendo assim, podemos melhorar nossa implementação abstraindo o código responsável pelo debounce:
Desse modo, desacoplamos toda a lógica de debounce em uma função chamada debounce()
que pode ser reutilizada em outros contextos da aplicação, como cliques em botão e mudanças da dimensão da janela da aplicação. Perceba também que a função debounce()
utiliza os conceitos de escopo léxico e closures. Sendo assim, ela retorna uma arrow function enquanto protege o valor a variável local timeoutId
, uma vez que o valor de timeoutId
vai persistir mesmo sendo reutilizado durante as chamadas da arrow function retornada.
Implementando debounce com React
Agora que vimos como implementar o debounce com JavaScript vanilla, chegou a hora que aprofundar um pouco mais e implementar um hook de debounce para uma aplicação desenvolvida com React. Para isso, precisamos do nosso input para aplicar o debounce:
No código acima criamos um input que utiliza o conceito de controlled components, utilizando um estado para persistir seu valor durante as renderizações da aplicação. Após isso podemos focar na implementação do hook:
O hook useDebounce()
segue a mesma lógica desenvolvida anteriormente para a função debounce()
. A sua diferença fica por conta do uso de uma referência utilizada para persistir o valor do timeoutId
sem disparar novas renderizações a cada mudança em seu valor. Agora podemos utilizar o hook useDebounce()
para atualizar um novo estado, uma vez que o estado value
continuará sendo utilizado para o feedback em tempo real do preenchimento do input:
Dessa forma, a cada 500ms
sem digitar uma tecla, o valor do estado debouncedValue
será atualizado e exibido no console do navegador. Vale notar que é necessário o uso de dois estados diferentes: uma para o preenchimento e feedback visual do input, e outro para uso em outras funções, como chamadas à uma API externa.
Conclusão
Ao implementar o debounce para eventos que ocorrem com frequência, como preenchimento de inputs de busca, é possível otimizar a performance da sua aplicação ao evitar múltiplas execuções de uma função. Vimos como criar uma função de debounce com JavaScript puro e um hook para aplicações em React. Contudo, essa mesma solução já foi desenvolvida por outras bibliotecas, como a lodash, prevendo outros contextos e caso de uso. Sendo assim, é possível avaliar se há a necessidade de implementar esse efeito, ou se é melhor utilizar uma biblioteca de terceiros para solucionar o seu problema.