'React not setting hook state despite using hook setter

I have a search bar input:

      <input
          type="text"
          className="px-4 py-2 w-80"
          placeholder="Search"
          onChange={(event) => searchWord(event.target.value)}
        />

I have this set up at the top of my file:

  const [foundWords, setFoundWords] = useState<any>([]);
  const [searchString, setSearchString] = useState('');

  const fetchWords = async () => {
    console.log("1", searchString);
    axios.get(`http://localhost:8081/find?searchString=${searchString.toLowerCase()}`)
      .then(({ data }) => setFoundWords(data));
  }

  const debounce = (fn: Function, ms = 300) => {
    let timeoutId: ReturnType<typeof setTimeout>;
    return function (this: any, ...args: any[]) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => fn.apply(this, args), ms);
    };
  };

  const searchWord = debounce((input: string) => {
    console.log("2", input)
    setSearchString(input);
    if (!input) return;
    fetchWords()
  }, 500);

The console log "1" above returns an empty string, but the console log "2" returns my search query.

What's going wrong with it?



Solution 1:[1]

You can use an useEffect instead of directly calling the fetchWords from the searchWord function

I would update your code the following way

  const [foundWords, setFoundWords] = useState<any>([]);
  const [searchString, setSearchString] = useState('');

  const fetchWords = useCallback((input: string) => {
    axios.get(`http://localhost:8081/find?searchString=${input.toLowerCase()}`)
      .then(({ data }) => setFoundWords(data));
  }, [])

  const debounce = (fn: Function, ms = 300) => {
    let timeoutId: ReturnType<typeof setTimeout>;
    return function (this: any, ...args: any[]) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => fn.apply(this, args), ms);
    };
  };

  const searchWord = debounce((input: string) => {
    setSearchString(input);
  }, 500)

  useEffect(() => {
    if (searchString) {
      fetchWords(searchString)
    }
  }, [searchString, fetchWords])

UseEffect would be guaranteed to trigger only after the searchString is updated. And as per useCallback for fetchWords. It is a better optimization and a good practice for functions used inside useEffect to be wrapped inside useCallback

Also, you can further optimize your code in the following way if it is not required to trigger render cycle again on change of searchString.

  const [foundWords, setFoundWords] = useState<any>([]);

  const fetchWords = useCallback((input: string) => {
    axios.get(`http://localhost:8081/find?searchString=${input.toLowerCase()}`)
      .then(({ data }) => setFoundWords(data));
  }, [])

  const debounce = (fn: Function, ms = 300) => {
    let timeoutId: ReturnType<typeof setTimeout>;
    return function (this: any, ...args: any[]) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => fn.apply(this, args), ms);
    };
  };

  const searchWord = debounce((input: string) => {
    if (input) {
      fetchWords(input)
    }
  }, 500)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1