reactjssearchnext.jsmeilisearch

Search component showing last result when input field is cleared using backspace


So I'm using Next.js and built a basic search page with input and storing the results after the query on a state array. The problem is when I clear the input field fast using backspace, it shows the result from the last keyword.

I believe I'm using the React state in the wrong manner.

Here is how I'm querying the search using meilisearch:

const [search, setSearch] = useState([]);
const [showSearch, setShowSearch] = useState(false);

async function onChange(e) {
    if (e.length > 0) {

      await client.index('sections')
        .search(e)
        .then((res) => {
          const list = res.hits.map((elm) => ({
            title: elm.Title,
            content: elm.formattedContent
          }));

          setSearch(list);
          setShowSearch(true);
        });
    } else {
      setSearch([]);
      setShowSearch(false);
    }
  }

and here is the input field and the search results:

<div className="searchPage wrapper">
        <input
          type="text"
          aria-label="Search through site content"
          placeholder="Search your keywords"
          onChange={(e) => onChange(e.target.value);}
        />

        {showSearch && (
          <div className="searchPageResults">
            <p className="suggested">Top Results</p>
            {search.length > 0 ? (
              <ul>
                {search.map((item, index) => (
                  <li key={`search-${index}`}>
                    <Link href={`/${item.slug}`}>
                      <a role="link">
                        {item.title}
                      </a>
                    </Link>

                    <p>{item.content}</p>
                  </li>
                ))}
              </ul>
            ) : (
              <p className="noResults">No results found</p>
            )}
          </div>
        )}
      </div>

What are the best practices to prevent this kind of situation?

You can check the live implementation here: https://budgetbasics.openbudgetsindia.org/search

To reproduce the issue:


Solution

  • Issue

    I suspect that when you quickly backspace that the last request made with "b" resolves asynchronously after the last onChange call is made where e.length > 0 is false. The search state is updated to an empty array and once the final asynchronous request resolves the search state is updated with the result of "b".

    Solution

    One possible solution would be to debounce the onChange handler so useless requests aren't made for fast typers. debounce from lodash is a common utility. I used a delay of 300ms, but this is obviously tunable to suit your needs and what feels best for you or your general users.

    import debounce from 'lodash/debounce';
    
    async function onChange(e) {
      if (e.length > 0) {
        await client.index('sections')
          .search(e)
          .then((res) => {
            const list = res.hits.map((elm) => ({
              title: elm.Title,
              content: elm.formattedContent
            }));
    
            setSearch(list);
            setShowSearch(true);
          });
      } else {
        setSearch([]);
        setShowSearch(false);
      }
    }
    
    const debouncedOnChange = useMemo(() => debounce(onChange, 300), []);
    
    ...
    
    <input
      type="text"
      aria-label="Search through site content"
      placeholder="Search your keywords"
      onChange={(e) => debouncedOnChange(e.target.value)} // <-- use debounced handler
    />