skip to Main Content

I’m using ReactSearchAutocomplete to display a list of 10 suggested names from a mySql query. The query is performed with a 100ms delay on user input, and it works fine but I’m clearly not handling the state properly because the query will return results fine, but upon calling setState, the search bar does not provide suggestions as would be expected.

Only if I then continue typing and trigger another query do the search results show up, and they seemingly are the results of the previous query.

My question is, how do I get the autocomplete component to properly render the suggested results from my query only AFTER setting the state of the namesList?

const SearchBar = () => {
const [namesList, setNamesList] = useState([]);

const handleOnSearch = (searchTerm) => {
  if (searchTerm.length < 2) {
    return;
  }

  let queryString = "url/api/search/" + searchTerm;
  if (isDev) {
    queryString = "http://localhost:3001/search/" + searchTerm;
  }

  fetch(queryString)
    .then((response) => response.json())
    .then((json) => {
      let searchResults = json.map((o) => ({ id: o.name, name: o.name }));
      setNamesList(searchResults);
    })
    .catch((error) => console.error(error));
};

  return (
    <div className="search-bar">
      <ReactSearchAutocomplete
        items={namesList}
        onSearch={handleOnSearch}
        autoFocus={true}
        inputDebounce={100}
        showNoResults={false}
      />
    </div>
  );
};

export default SearchBar;

2

Answers


  1. Some time it can be face during the dangling the state updates and I’ll give some suggestions fix your issue,

    1.Be careful handling the State update,

    The state should be update asynchronously, and then component should re render after update the state, and careful while called the setNamesList and update nameList. and make sure the ReactSearchAutocomplete component properly listens to the changes of nameList , because when the component doesn’t catch the changes of state as expected then it will not re render.

    2.Check the doc and version of the ReactSearchAutocomplete .

    3.Check the component while updating the state changes , you can check it using console like that.

    import React, { useState } from "react";
    import { ReactSearchAutocomplete } from 'react-search-autocomplete';
    
    const SearchBar = () => {
      const [namesList, setNamesList] = useState([]);
    
      const handleOnSearch = (searchTerm) => {
        if (searchTerm.length < 2) {
          return;
        }
    
        let queryString = "url/api/search/" + searchTerm;
        if (process.env.NODE_ENV === 'development') {
          queryString = "http://localhost:3001/search/" + searchTerm;
        }
    
        fetch(queryString)
          .then((response) => response.json())
          .then((json) => {
            let searchResults = json.map((o) => ({ id: o.name, name: o.name }));
            console.log("Search results:", searchResults); // check the state log
            setNamesList(searchResults);
          })
          .catch((error) => console.error(error));
      };
    
      return (
        <div className="search-bar">
          <ReactSearchAutocomplete
            items={namesList}
            onSearch={handleOnSearch}
            autoFocus={true}
            inputDebounce={100}
            showNoResults={false}
          />
        </div>
      );
    };
    
    export default SearchBar;
    

    4.And you can use the useEffect for check the state changes

    import React, { useState } from "react";
    import { ReactSearchAutocomplete } from 'react-search-autocomplete';
    
    const SearchBar = () => {
      const [namesList, setNamesList] = useState([]);
    
      const handleOnSearch = (searchTerm) => {
        if (searchTerm.length < 2) {
          return;
        }
    
        let queryString = "url/api/search/" + searchTerm;
        if (process.env.NODE_ENV === 'development') {
          queryString = "http://localhost:3001/search/" + searchTerm;
        }
    
        fetch(queryString)
          .then((response) => response.json())
          .then((json) => {
            let searchResults = json.map((o) => ({ id: o.name, name: o.name }));
            console.log("Search results:", searchResults); // check the state log
            setNamesList(searchResults);
          })
          .catch((error) => console.error(error));
      };
    
      return (
        <div className="search-bar">
          <ReactSearchAutocomplete
            items={namesList}
            onSearch={handleOnSearch}
            autoFocus={true}
            inputDebounce={100}
            showNoResults={false}
          />
        </div>
      );
    };
    
    export default SearchBar;
    
    Login or Signup to reply.
  2. The issue is that the library you are using does not allow for dynamic data. This is one of their earliest issues. They will not be working towards it. The whole library was built keeping static requirements in mind.

    You have the option to use some other library MUI autocomplete. They support server calls.

    You can also make your own component, if the requirement is not that complex.

    Basically what you are supposed to have is a:

    1. a controlled input field
    2. list of divs to map over the items
    3. logic to run API call in denounced manner
    const debounceWait = 1000; //debounce time, you can keep it inside the component too
    const AutoCompleteDropdown = () => {
      const [query, setQuery] = useState("");
      const [options, setOptions] = useState([]);
      const ref = useRef(query); //ref to match query with input
      const debounceRef = useRef(null); //ref to cancel existing timeouts for the fetch
      const onSearch = (e) => {
        let string = e.target.value;
        setQuery(string);
      };
    
      useEffect(() => {
        ref.current = query; //updating ref for the query
        clearTimeout(debounceRef.current);
        if (query.length < 2) {
          setOptions([]);
        } else {
          debounceRef.current = setTimeout(() => {
            clearTimeout(debounceRef.current);
            fetch(`https://dummyjson.com/posts/search?q=${query}`)
              .then((res) => res.json())
              .then((json) => {
                if (ref.current === query) //this check is necessary in case one server call started earlier finishes later then the other server call
                  setOptions(json.posts.map((x) => x.title));
              })
              .catch((error) => console.error(error));
          }, debounceWait);
        }
      }, [query]);
      console.log({ options });
      return (
        <div className="dropdown">
          <div className="control">
            <div className="selected-value">
              <input
                type="text"
                value={query}
                name="searchTerm"
                onChange={onSearch}
              />
            </div>
          </div>
    
          <div>
            {options.map((option, index) => {
              return <div>{option}</div>;
            })}
          </div>
        </div>
      );
    };
    

    Here is a demo


    The above example should work for your use case. There is an inefficiency that API calls are not being cancelled. But that is not a direct requirement you have mentioned so I have not worked towards it.

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