Search code examples
javascriptreactjsuse-effectuse-state

ReactJS: React setState/useState does not update immediately


I'm building a Food Recipe app, I have an instant auto filter search result that query and get data when the user types any of letter. For example, with letter "c" it will show list of results which have letter "c" in the ingredients. Everything seems to work fine, except the auto filter is delayed one letter backward, so it only show results that contain letter "c" when user type "ch" or "c + one random letter".

I read this link: https://linguinecode.com/post/why-react-setstate-usestate-does-not-update-immediatelyand that helps me define why React useState does not update immediately, and I think the solution is to used React useEffect. I'm new in learning React so I have trouble to refactor my code into using useEffect in my case. I would appreciate if someone enlighten me. Thank you everyone!

Here's my demo: gif

Here's my code:

// Recipes.js

export default function Recipes() {

  const [query, setQuery] = useState("")
  const [recipes, setRecipes] = useState([])
  const [alert, setAlert] = useState("")

  const APP_ID = "5b1b4741"
  const APP_KEY = "0325f7a61ac15027151b8060740d90f0"

  const url = `https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`    

  const getData = async () => {
    if (query !== "") {
      const result = await Axios.get(url)
      if (!result.data.more) {
        return setAlert("No food with such name")
      }
      console.log(result)
      setRecipes(result.data.hits)
      setQuery("")
      setAlert("")
    } else {
      setAlert("Please fill the form")
    }
  }
    
  const onChange = async e => {
    setQuery(e.target.value)
    if (query !== "") {
      const result = await Axios.get(url)
      console.log(result)
      setRecipes(result.data.hits)
    }
  }

  const onSubmit = e => {
    e.preventDefault()
    getData()
  };

  return (
    <div className="recipes">
      <div className="search-box">
        <h1>Your Recipe App</h1>

        <form onSubmit={onSubmit} className="search-form">
          {alert !== "" && <Alert alert={alert} />}

          <input
            type="text"
            name="query"
            onChange={onChange}
            value={query}
            placeholder="Search Food"
          />
          <input type="submit" value="Search" />
        </form>
        
        {query.length !== 0 && 
          <div className="search-result">
            {recipes.slice(0, 5).map((val) => {
              return (
                <a className="search-item" href={val.recipe.url} target="_blank" rel="noopener noreferrer">
                  <p>{val.recipe.label}</p>
                </a>
              ) 
            })}
          </div>
        }
      </div>

      <div className="recipes-card">
        {recipes !== [] &&
          recipes.map(recipe => <Recipe key={uuidv4()} recipe={recipe} />)}
      </div>
    </div>

// Recipe.js

const Recipe = ({ recipe }) => {
  const [show, setShow] = useState(false);
  const { label, image, url, ingredients } = recipe.recipe;

  return (
    <div className="recipe">
      <h2>{label}</h2>
      <img src={image} alt={label} />
      <a href={url} target="_blank" rel="noopener noreferrer">
        Go To Link
      </a>
      <button onClick={() => setShow(!show)}>Ingredients</button>
      {show && <RecipeDetails ingredients={ingredients} />}
    </div>
  )
}

export default Recipe

Solution

  •   const onChange = async e => {
        setQuery(e.target.value)
        if (query !== "") {
          const result = await Axios.get(url)
          console.log(result)
          setRecipes(result.data.hits)
        }
      }
    

    You're sending the message to set a new value of query but then immediately using the old value (which the function has closed over) to make your Ajax request.

    Either:

    • Just use e.target.value instead of query (don't forget to move the logic that calculates the value of url too).
    • Set up a useEffect hook with a dependency on query which has the code from lines 3–7 of what I quoted.