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
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:
e.target.value
instead of query
(don't forget to move the logic that calculates the value of url
too).useEffect
hook with a dependency on query
which has the code from lines 3–7 of what I quoted.