Debounce en JavaScript

El debouncing es una técnica utilizada en ingeniería de interfaces para tratar las pulsaciones (o spikes) eléctricas que pueden aparecer en los dispositivos electrónicos, especialmente en los interruptores y botones. Estas pulsaciones pueden ser interpretadas como múltiples

Leonidas Esteban

Leonidas Esteban

Debounce en JavaScript

aquí puedes encontrar el código de todos los ejemplos https://github.com/LeonidasEsteban/debounce-javascript-react

El debouncing es una técnica utilizada en ingeniería de interfaces para tratar las pulsaciones (o spikes) eléctricas que pueden aparecer en los dispositivos electrónicos, especialmente en los interruptores y botones. Estas pulsaciones pueden ser interpretadas como múltiples pulsaciones en lugar de una sola, lo que puede dar lugar a errores en la detección de la acción del usuario. El debouncing consiste en esperar un período de tiempo corto después de una pulsación para asegurarse de que se ha producido una sola acción y no varias pulsaciones sucesivas.

Debounce en el Frontend

En el desarrollo de interfaces de usuario con JavaScript, el debouncing se refiere a una técnica utilizada para limitar la frecuencia con la que se puede ejecutar una función o evento. Esto se hace para evitar que la función se ejecute demasiado a menudo en respuesta a una acción del usuario, como una pulsación en un botón o un evento de arrastre y soltar.

El debouncing se implementa esperando un tiempo determinado después de cada invocación de la función antes de permitir una nueva ejecución. Si durante ese período de tiempo se invoca de nuevo la función, se resetea el contador y se espera el período de tiempo completo antes de permitir la próxima ejecución. De esta manera, se asegura que la función se ejecute solo una vez después de que haya finalizado la secuencia de acciones del usuario.

Debounce en JavaScript

En este ejemplo se quiere buscar el nombre de un pokemon pero sin la necesidad de tener un botón de "submit" y en vez de eso haga las búsquedas cada que el usuario vaya escribiendo una letra.

Sin debounce haríamos un request a la base de datos por cada letra ingresada

<input type="search" id="search" placeholder="Quién es ese pokemon">
  <script>
    const searchInput = document.querySelector('#search');
    let timeoutId;

    searchInput.addEventListener('input', function () {
      clearTimeout(timeoutId);

      // Aquí se puede hacer la solicitud a la base de datos
      console.log(`Realizando solicitud a la base de datos con "${searchInput.value}"`);

    });
  </script>
Sin debounceSin debounce

Con debounce tendríamos un tiempo de tolerancia, en este caso 500 milisegundos o medio segundo el cual consideramos tolerable esperar para hacer un request ya que una persona escribe mucho más rápido de eso. Ganamos performance de base de dato y renderizado en la interfaz


const searchInput = document.querySelector('#search');
let timeoutId;

searchInput.addEventListener('input', function() {
  clearTimeout(timeoutId);

  timeoutId = setTimeout(function() {
    // Aquí se puede hacer la solicitud a la base de datos
    console.log(`Realizando solicitud a la base de datos con "${searchInput.value}"`);
  }, 500);
});
Simple debounce en JavaScriptSimple debounce en JavaScript

Debounce como utilidad

Podemos mejorar este código para que el setTimeout / debounce sea reutilizable en cualquier parte de la aplicación donde quisieramos un comportamiento similar

<input type="search" id="search" placeholder="Quién es ese pokemon">

  <script>
    function debounce(func, delay) {
      let timer;
      return function (...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          func.apply(this, args);
        }, delay);
      };
    }

    const searchInput = document.getElementById("search");

    function handleChange(e) {
      console.log(e.target.value);
    }

    searchInput.addEventListener("input", debounce(handleChange, 500));
  </script>

Debounce en React.js

Traslademos las ideas previas a React y contemos los requests en la pantalla. ahora en vez de una utilidad simple hemos creado un custom hook llamado useDebounce

De forma ilustrativa se ha incluido el ejemplo sin debounce para que se perciba perfectamente la mejora de performance

Debounce vs No debounce en React.jsDebounce vs No debounce en React.js
import React, { useState, useEffect } from "react";

const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
};

const App = () => {
  const [query, setQuery] = useState("");
  const [pokemon, setPokemon] = useState({});
  const [loading, setLoading] = useState(false);
  const [requestCount, setRequestCount] = useState(0);

  const debouncedQuery = useDebounce(query, 500);

  useEffect(() => {
    async function fetchPokemon() {
      if (debouncedQuery.trim() === "") {
        setPokemon({});
        return;
      }
      setLoading(true);
      setRequestCount(requestCount + 1);
      const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${debouncedQuery.toLowerCase()}`);
      const data = await res.json();
      setPokemon(data);
      setLoading(false);
    }
    fetchPokemon();
  }, [debouncedQuery]);

  return (
    <div className="pokedex">
      <h1>Pokédex</h1>
      <input
        type="text"
        placeholder="Enter Pokémon name or number"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <p>Total requests: {requestCount}</p>
      {loading && <div className="loading">Loading...</div>}
      {pokemon.name && (
        <div className="results">
          <h2>{pokemon.name}</h2>
          <div
            className="sprite"
            style={{ backgroundImage: `url(${pokemon.sprites.front_default})` }}
          />
          <p>Weight: {pokemon.weight}</p>
          <p>Height: {pokemon.height}</p>
        </div>
      )}
    </div>
  );
};

export default App;

Este ejemplo con React implementa la función useDebounce como un hook personalizado que encapsula la lógica de debounce. Este hook acepta dos parámetros: el valor que se desea debouncear y el tiempo de espera en milisegundos. Devuelve un valor que se actualiza después de que el valor de entrada no ha cambiado durante el tiempo especificado.

Luego, utilizamos este hook personalizado en un componente de React que representa una aplicación simple de búsqueda de Pokémon. La aplicación tiene un campo de entrada donde se puede escribir el nombre de un Pokémon y una lista de resultados que muestra los nombres de los Pokémon coincidentes.

Cada vez que se escribe en el campo de entrada, se llama al hook useDebounce para esperar 500 milisegundos antes de realizar una búsqueda en la PokeAPI. Si el valor de entrada cambia antes de que expire el tiempo de espera, se reinicia el temporizador. Una vez que se alcanza el tiempo de espera, se realiza la búsqueda y se actualiza la lista de resultados. La aplicación también muestra el número total de solicitudes realizadas para ayudar a monitorear el uso de recursos.

En general, el ejemplo muestra cómo el debounce puede mejorar la usabilidad y el rendimiento de una aplicación, especialmente en casos de uso donde la entrada del usuario puede ser frecuente o intensiva en recursos.

Ilustracion que representa como crece alguien profesionalmente

Entérate de las últimas novedades

Streamings, Noticias y Early Adopter bonus. Sé el primero en enterarte de todo.