0

Why is my useEffect react function running on every page load although giving it a second value array with a query variable?

useEffect( () => {
    getRecipes();
}, [query]);

Shouldn't it only run when the query state variable changes? I have nothing else using the getRecipes function except of the useEffect function.

import React, {useEffect, useState} from 'react';
import './App.css';
import Recipes from './components/Recipes/Recipes';

const App = () => {

    // Constants
    const APP_ID                                = '111';
    const APP_KEY                               = '111'; 
    const [recipes, setRecipes]                 = useState([]);
    const [search, setSearch]                   = useState(''); 
    const [query, setQuery]                     = useState('');
    const [showRecipesList, setShowRecipesList] = useState(false);

    // Lets
    let recipesList                             = null; 

    // Functions
    useEffect( () => {
        getRecipes();
    }, [query]);

    // Get the recipie list by variables
    const getRecipes = async () => {
        const response = await fetch(`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}&from=0&to=3&calories=591-722&health=alcohol-free`); 
        const data     = await response.json(); 
        console.log(data.hits);
        setRecipes(data.hits);
    }

    // Update the search constant 
    const updateSearch = e => {
        console.log(e.target.value);
        setSearch(e.target.value);
    }

    const runQuery = e => {
        e.preventDefault();
        setQuery(search);
    }


    // List recipes if ready
    if (recipes.length) {
        console.log(recipes.length);
        recipesList = <Recipes recipesList={recipes} />
    }

    return (

        <div className="App">

            <form className='search-app' onSubmit={ runQuery }>
                <input 
                    type='text' 
                    className='search-bar' 
                    onChange={ updateSearch } 
                    value={search}/>
                <button 
                    type='submit'   
                    className='search-btn' > Search </button>
            </form>

            <div className='recipesList'>
                {recipesList}
            </div>

        </div>

    );

}

export default App;

Following this: https://www.youtube.com/watch?v=U9T6YkEDkMo

n4m31ess_c0d3r
  • 3,028
  • 5
  • 26
  • 35
Imnotapotato
  • 5,308
  • 13
  • 80
  • 147
  • 2
    All useEffects run once when the component mounts, and then they only rerun when one of their dependencies (in the array) changes. – JMadelaine Mar 07 '20 at 09:52
  • 1
    If you want to only call getRecipes when the query has a certain value, then use an if function inside the use effect. – JMadelaine Mar 07 '20 at 09:54
  • 1
    `useEffect( () => { if (query) { getRecipes(); }}, [query]);` <== This worked. thank you. you can comment for an upvote ;) – Imnotapotato Mar 07 '20 at 09:59
  • @JMadelaine technically, isn't `useEffect()` executed the first time when it is the `render()` lifecycle? The `componentWillMount()`, and then `render()`, and then `componentDidMount()` – nonopolarity Mar 07 '20 at 10:11
  • @Rick Sanchez I've added my comments as an answer along with more information. – JMadelaine Mar 07 '20 at 11:37
  • Also refer to this post to stop initial render --- https://stackoverflow.com/questions/53253940/make-react-useeffect-hook-not-run-on-initial-render – n4m31ess_c0d3r Mar 07 '20 at 13:17

2 Answers2

2

A useEffect is the equivalent of componentDidMount, so it will run once when the component mounts, and then only re-run when one of the dependencies defined in the dependency array changes.

If you want to call getRecipes() only when the query dependency has a value, you can call it in a conditional like so:

useEffect(() => {
  if(query) {
    getRecipes()
  }
}, [query])

Also, as your useEffect is calling a function (getRecipes) that is declared outside the use effect but inside the component, you should either move the function declaration to be inside the useEffect and add the appropriate dependencies, or wrap your function in a useCallback and add the function as a dependency of the useEffect.

See the React docs for information on why this is important.

JMadelaine
  • 2,859
  • 1
  • 12
  • 17
0

UseEffect hook work equivalent of componentDidMount, componentDidUpdate, and componentWillUnmount combined React class component lifecycles.but there is a different in time of acting in DOM.componentDidMount and useEffect run after the mount. However useEffect runs after the paint has been committed to the screen as opposed to before. This means you would get a flicker if you needed to read from the DOM, then synchronously set state to make new UI.useLayoutEffect was designed to have the same timing as componentDidMount. So useLayoutEffect(fn, []) is a much closer match to componentDidMount() than useEffect(fn, []) -- at least from a timing standpoint. Does that mean we should be using useLayoutEffect instead? Probably not. If you do want to avoid that flicker by synchronously setting state, then use useLayoutEffect. But since those are rare cases, you'll want to use useEffect most of the time.

Fazel Farnia
  • 161
  • 2
  • 8