skip to Main Content

I have created a typeahead using react. The issue is that, if i put something and type backspace, some previous results are showing.

I understood the reason is thar, Since the API call are async , its rendering some previous call results. So how we could we able to resolve this issue effectively ?

codeSandbox : https://codesandbox.io/p/sandbox/typeahead-3xrg9y

import React, { useEffect, useState } from "react";
import { fetchSuggestions } from "./API";

export default function Typeahead() {
  const [searchQuery, setSearchQuery] = useState("");
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (!searchQuery) {
      setResults([]);
      return;
    }
    getResults(searchQuery);
  }, [searchQuery]);

  async function getResults(query) {
    const result = await fetchSuggestions(query);
    if (query === searchQuery) {
      setResults(result);
    }
  }

  function inputHandler(e) {
    const query = e.target.value;
    setSearchQuery(query);
  }

  return (
    <div className="container">
      <input onChange={inputHandler} value={searchQuery} />
      <div>
        <ul className="list-container">
          {results.map((res) => (
            <li>{res}</li>
          ))}
        </ul>
        <div />
      </div>
    </div>
  );
}

2

Answers


  1. Chosen as BEST ANSWER

    If anyone gets into same situation, this issue can be resolved using useRef hook. To get the current value in the callback time, we will store the input in a ref as well. then compare the input with the ref instead of state value.

    import React, { useEffect, useRef, useState } from "react";
    import { fetchSuggestions } from "./API";
    
    export default function Typeahead() {
      const [searchQuery, setSearchQuery] = useState("");
      const [results, setResults] = useState([]);
      const searchQueryRef = useRef("");
    
      useEffect(() => {
        if (!searchQuery) {
          setResults([]);
          return;
        }
        getResults(searchQuery);
      }, [searchQuery]);
    
      async function getResults(query) {
        const result = await fetchSuggestions(query);
        if (query === searchQueryRef.current) {
          setResults(result);
        }
      }
    
      function inputHandler(e) {
        const query = e.target.value;
        setSearchQuery(query);
        searchQueryRef.current = query;
      }
    
      return (
        <div className="container">
          <input onChange={inputHandler} value={searchQuery} />
          <div>
            <ul className="list-container">
              {results.map((res) => (
                <li>{res}</li>
              ))}
            </ul>
            <div />
          </div>
        </div>
      );
    }


  2. You just need to add some debounce for the input, using the setTimeout that resets every time the input changes and only makes a call to fetch suggestions after 300 ms ( you can change as needed ) of inactivity

    import React, { useEffect, useState } from "react";
    import { fetchSuggestions } from "./API";
    
    export default function Typeahead() {
      const [searchQuery, setSearchQuery] = useState("");
      const [results, setResults] = useState([]);
      const [timerId, setTimerId] = useState(null); // State to keep track of the timeout ID
    
      useEffect(() => {
        if (!searchQuery) {
          setResults([]);
          return;
        }
    
        // Clear the existing timeout if there is one
        if (timerId) {
          clearTimeout(timerId);
        }
    
        const newTimerId = setTimeout(() => {
          getResults(searchQuery);
        }, 300);
    
        setTimerId(newTimerId);
    
        // Cleanup function to clear the timeout if the component is unmounted
        return () => clearTimeout(newTimerId);
      }, [searchQuery]);
    
      async function getResults(query) {
        const result = await fetchSuggestions(query);
        if (query === searchQuery) {
          setResults(result);
        }
      }
    
      function inputHandler(e) {
        const query = e.target.value;
        setSearchQuery(query);
      }
    
      return (
        <div className="container">
          <input onChange={inputHandler} value={searchQuery} />
          <div>
            <ul className="list-container">
              {results.map((res) => (
                <li key={res}>{res}</li>
              ))}
            </ul>
            <div />
          </div>
        </div>
      );
    }
    
    

    CODESANDBOX

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search