Home > Software engineering >  React JS setTimeout gets executed multiple times
React JS setTimeout gets executed multiple times

Time:06-01

Hi everyone I'm new to React JS and I got stuck into a problem that cant figure out what is the cause. So I'm trying to make a search field component that only trigger an API call after a user is done typing. What happened is that after the search is keyed in setTimeout gets executed multiple times. I'm afraid this would make multiple unnecessary api call. Would very much appriciae your help. Thank you!

main.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <App />
)

App.tsx

import { useState } from 'react'
import fetchData from './utils/fetch'
import { WeatherModel } from './model/Weather'

function SearchField() {
  let timer = 0
  const [city, setCity] = useState('New York')

  const handleFetchWeather = async () => {
    const data: WeatherModel = await fetchData(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${WEATHER_API_KEY}&units=metric`)
    console.log(data)
  }

  const handleSetCity = (e) => {
    if(timer) clearTimeout(timer)
    setCity(e.target.value)
    timer = setTimeout(() => {
      console.log(city)
      // handleFetchWeather()
    }, 2000)
  }

  return (
    <div className="search">
      <input type="text" placeholder="Search city..." onChange={handleSetCity} />
    </div>
  )
}

export default SearchField

Muliple console logs after search is keyed in enter image description here

CodePudding user response:

I would agree with the debounce solution, but also if you didn't know it, this code might be even simpler for you, so each time a component rerenders, the timer is set with 0 again, so in order to persist the value, imagine you need to put it in a state, just like this:

import { useState } from "react";

function SearchField() {
  const [city, setCity] = useState("New York");
  const [timer, setTimer] = useState(0);

  const handleFetchWeather = async () => {
    const data = await fetch(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${WEATHER_API_KEY}&units=metric`
    );
    console.log(data);
  };

  const handleSetCity = (e) => {
    if (timer) {
      clearTimeout(timer);
    }
    setCity(e.target.value);
    setTimer(
      setTimeout(() => {
        console.log(city);
        // handleFetchWeather()
      }, 2000)
    );
  };

  return (
    <div className="search">
      <input
        type="text"
        placeholder="Search city..."
        onChange={handleSetCity}
      />
    </div>
  );
}

export default SearchField;

Now, every rerender, the component will remember the previous value of it ;)

CodePudding user response:

Why don't you use Debounce:

https://blog.logrocket.com/how-and-when-to-debounce-or-throttle-in-react/

Something like this:

useEffect(() => {
let debounce;
if (debounce) {
  clearTimeout(debounce);
  debounce = null;
}

debounce = setTimeout(() => {
  // DO SOMETHING AFTER TIMEOUT ENDS
  setSearchEntry(searchValue);
  }, 1 * 1000);
}, [setSearchEntry, searchValue]);

Good luck :)

CodePudding user response:

The onChange event in the input field will be fired on each new character input, learn more here.

I think the best solution, in this case, is to add submit button that fires handleSetCity() and your onChange should set a state variable so it's easier to handle the data.

  • Related