Search code examples
javascriptreactjsdebouncing

Custom debounce function does not pass e.target.value in React


Basics

I created a simple component with a search field and some state to update the users input. In order to (theoretically) improve performance, I wrote a debounce function to prevent the handleChange from firing on every keystroke.

Problem

e.target.value is always an empty string when the function fires. I tried to use e.persist() to make the component remember the value, but it gets lost somewhere. So the <input /> appears always empty.

Code

const ShopSearch = () => {
  const [search, setSearch] = React.useState("");

  const handleChange = e => {
    e.persist();
    const value = e.target.value;
    setSearch(value);
  };

  function debounce(func) {
    let timer;

    return (...args) => {
      clearTimeout(timer);

      timer = setTimeout(() => func.apply(this, args), 300);
    };
  }

  return (
    <div>
      <form role="search">
        <input
          placeholder="Search for a shop"
          value={search}
          onChange={debounce(handleChange)}
        />
      </form>

      <Query url="https://shop-search-api.blabla.com/shopsearch?searchTerm=hemden&page=0">
        {({ shops }) =>
          shops
            .filter(shop =>
              search.length > 1
                ? shop.shopName.toLowerCase().includes(search.toLowerCase())
                : false
            )
            .map(shop => <ShopOverview key={shop.tsID} {...shop} />)
        }
      </Query>
    </div>
  );
};

What am I doing wrong? I guess the synthetic event gets lost somewhere because it is not there anymore when the function gets finally executed? Can someone explain it to me or point me to some resources?


Solution

  • You are using debounce function inside the component which makes it re-eveluate everytime when the component renders.

    Try keeping it either outside of the Component or in the helper functions.

    And also use useCallback hook to have a function which doesn't change on App rerenders

    
    function debounce(func) {
      let timer;
    
      return (...args) => {
        clearTimeout(timer);
    
        timer = setTimeout(() => func.apply(this, args), 300);
      };
    }
    
    const ShopSearch = () => {
      const [search, setSearch] = React.useState("");
    
      const handleChange = e => {
        e.persist();
        const value = e.target.value;
        setSearch(value);
      };
    
      const debouncedHandleChange = React.useCallback(
        debounce(handleChange, 500),
        []
      );
    
      return (
        <div>
          <form role="search">
            <input
              placeholder="Search for a shop"
              value={search}
              onChange={debouncedHandleChange}
            />
          </form>
    
          <Query url="https://shop-search-api.blabla.com/shopsearch?searchTerm=hemden&page=0">
            {({ shops }) =>
              shops
                .filter(shop =>
                  search.length > 1
                    ? shop.shopName.toLowerCase().includes(search.toLowerCase())
                    : false
                )
                .map(shop => <ShopOverview key={shop.tsID} {...shop} />)
            }
          </Query>
        </div>
      );
    };