React Hooks: Part 2 and Recipe App
Tags: react, codenewbie, webdev, javascript
Published: 2021-01-30 20:15:27

*Originally I have submitted this tutorial to Facebook Community Challenge 2020, you can access and read it from [this link](https://hulyak.github.io/react-tutorial/).*


If you’ve written React class components before, you should be familiar with lifecycle methods like `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount`. The `useEffect` Hook is all three of these lifecycle methods combined. It's used for side effects (all things which happen outside of React) like network requests, managing subscriptions, DOM manipulation, setting up event listeners, timeouts, intervals, or local storage, etc.

`useEffect` functions run after every rerender by default.
It doesn't matter what caused the render like changing the state, or maybe new props, the effect will be triggered after rendering.

Setting the title of the page will also be a side effect.
`useEffect` takes a callback function, we can tell `useEffect` when the code we want to be executed with the second argument. This will control the effect.

For the second argument, we can use `useEffect` in three different ways:
 
## 1. useEffect without a Dependency Array

```js
// runs after every rerender
useEffect(() => {
  console.log('I run after every render and at initial render');
});
```

This renders every time our app renders and at initial render. But we don't want to render each time, this can cause _an infinite loop_ and we should avoid this.

We need to optimize our components. We can pass _a list of dependencies_. The dependency will trigger an effect on the change of the dependencies.

Let's see it in a simple example.

```js
// src/components/UseEffect.js

import React, { useState, useEffect } from 'react';

const UseEffect = ()  => {
  const [count, setCount] = useState(0);
  const [isOn, setIsOn] = useState(false;)

// useEffect to set our document title to isOn's default state
  useEffect(() => {
    document.title = isOn;
    console.log('first render');
  });

const handleClick = () => {
  setIsOn(!isOn);
  setCount(count + 1)
}
  return (
    <div>
      <h1>{isOn ? "ON" : "OFF"}</h1>
      <h1>I was clicked {count} times</h1>
      <button onClick={handleClick} className="btn">Click me</button>
    </div>
  );
}

export default UseEffect;
```

In our example, we have two states: `count` and `isOn`. We are rendering these with our `button` and `h1` tags. When the button gets clicked, we are setting the `isOn` state to the opposite of its state.

For the purpose of this example, we are setting `useEffect` hook and changing our document title to our `isOn`'s default value(false).

With our `console.log`, we can see that we rerender our component with our initial render and whenever we click the button. Because we don't have any array dependency.

## 2. useEffect with an Empty Dependency Array

```javascript
// runs at initial render
useEffect(() => {
  console.log('I only run once');
}, []);
```

This only runs once when the component is mounted or loaded.

It looks exactly like the behavior of `componentDidMount` in React classes. But we shouldn't compare with React class components.

## 3. useEffect with a Non-empty Dependency Array 

```javascript
// runs after every rerender if data has changed since last render
useEffect(() => {
  console.log('I run whenever some piece of data has changed)');
}, [id, value]);
```

If the variable is inside this array, we will trigger this effect only when the value of this variable changes, and not on each rerender. Any state or props we list in this array will cause `useEffect` to re-run when they change.

We can put our variables inside the dependency array from our component like any variables that we want for; for example, state variables, local variables, or props.
They adjust the array of dependencies.

```js
// src/components/UseEffect.js

import React, { useState, useEffect } from 'react';

const UseEffect = () => {
    const [ count, setCount ] = useState(0);
    const [ isOn, setIsOn ] = useState(false);

    useEffect(() => {
      document.title = isOn;
      // only difference from our previous example
      setCount(count + 1);
    });

    const handleClick = () => {
      setIsOn(!isOn);
    };
    
    return (
      <div>
        <h1>{isOn ? 'ON' : 'OFF'}</h1>
        <h1>I was clicked {count} times</h1>
        <button onClick={handleClick} className="btn">Click me</button>
      </div>
    );
}

export default UseEffect;
```

We have just changed one line of code from the previous example and changed `useEffect` a little, we will not increase our count with the button click. However, we will  trigger our effect whenever the `useEffect` triggers. Let's see what will happen. 

![loop.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1609196578308/gt-sjPVdw.gif)

![source.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1609196592710/fuj4MYsX_.gif)


We are in an infinite loop; but why? React rerenders our component when the state changes. We are updating our state in our `useEffect` function, and it's creating an infinite loop.  

I think no one wants to stuck in a loop; so, we need to find a way to get out of the loop and only run our function whenever our `isOn` state changes. For that, we will add our dependency array and pass our `isOn` state.

The array of variables will decide if it should execute the function or not. It looks at the content of the array and compares the previous array, and if any of the value specified in the array changes compared to the previous value of the array, it will execute the effect function. If there is no change, it will not execute. 


```js
// src/components/UseEffect.js

import React, { useState, useEffect } from 'react';

const UseEffect = () =>  {
    const [ count, setCount ] = useState(0);
    const [ isOn, setIsOn ] = useState(false);

    useEffect(() => {
      document.title = isOn;
      setCount(count + 1);
      // only add this
    }, [isOn]);

    const handleClick = () => {
      setIsOn(!isOn);
    };
    
    return (
      <div>
        <h1>{isOn ? 'ON' : 'OFF'}</h1>
        <h1>I was clicked {count} times</h1>
        <button onClick={handleClick} className="btn">Click me</button>
      </div>
    );
  }

export default UseEffect;
```

![dep.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1609196618759/XhZhHZeOZ.gif)

It seems like working, at least we got rid of the infinite loop, when it updates `count` it will rerender the component. But if you noticed, we start counting from _1_ instead of _0_. We render first at initial render, that's why we see _1_. This effect behaves as a `componentDidMount` and `componentDidUpdate` together. We can solve our problem by adding an `if` condition.

```JavaScript
 if(count === 0 && !isOn) return;
 ```

This will only render at the first render, after that when we click the button, `setIsOn` will be true. Now, our code looks like this.

```javascript
import React, { useState, useEffect } from 'react';

const UseEffect = () =>  {
    const [ count, setCount ] = useState(0);
    const [ isOn, setIsOn ] = useState(false);

    useEffect(() => {
      document.title = isOn;
      // add this to the code
      if(count === 0 && !isOn) return;
      setCount(count + 1);
    }, [isOn]);

    const handleClick = () => {
      setIsOn(!isOn);
    };
    return (
      <div>
        <h1>{isOn ? 'ON' : 'OFF'}</h1>
        <h1>I was clicked {count} times</h1>
        <button onClick={handleClick} className="btn">Click me</button>
      </div>
    );
  }

export default UseEffect;
```


![dep2.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1609196655442/KirDLclFe.gif)

Okay, now it starts from 0. If you're checking the console, you may see a warning: 

![warning.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1609196665966/90cK0k_oV.png)

We will not add `count` inside our dependency array because if the count changes, it will trigger a rerender. This will cause an _infinite loop_. We don't want to do this, that's why we will not edit our `useEffect`. If you want, you can try it out.

## `useEffect` Cleanup

`useEffect` comes with a cleanup function that helps unmount the component, we can think of it is like `componentWillUnmount` lifecycle event. When we need to clear a subscription or clear timeout, we can use cleanup functions. When we run the code, the code first will clean up the old state, then will run the updated state. This can help us to remove unnecessary behavior or prevent memory leaking issues.

```javascript
useEffect(() => {
  effect;
  return () => {
    cleanup;
  };
}, [input]);
```

```js
// src/components/Cleanup.js

import React, { useState, useEffect } from 'react';

const Cleanup = ()  => {
  const [ count, setCount ] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
      }, 1000);

      // cleanup function
    return () => clearInterval(intervalId);
  }, []);

  return (
  <div>
    <h1>{count}</h1>
  </div>
  );
}

export default Cleanup;
```
We have defined a `setInterval` method inside our `useEffect` hook, and our interval will run in the background. We are passing a function inside `setInterval` and it will update our `count` piece of state every second.
Our `useEffect` hook is only gonna run one time because we have our empty dependency array.

![cleanup.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1609196688633/UYws6xaBa.gif)

To clean up our hook, we are creating our `return` function, getting our interval id, and passing inside our `clearInterval` method.

- We can use multiple **useEffect's** in our application.
- We cannot mark **useEffect** as an **async function**.
- React applies effect in the order they are created.
- We can make API calls to React in four different ways:

1. Call **fetch/Axios** in your component
2. Make another file and store your API calls.
3. Create a reusable custom hook.
4. Use a library like **react-query**, **SWR**, etc.


We will use `fetch` in our application for simplicity. Now, ready to move on with our final demo app? Time to combine everything you have learned with a real-life application. This will be fun!!! 😇

## RECIPE APP

It's time to create our demo app!
We will create a Food Recipe app, we will fetch data from an API and we will use both `useState` and `useEffect` hooks.

First, create a new file under `src > components` and name it `FoodRecipe.js`.
To be able to get a response for search queries, we need an `APP ID` and an `APP KEY`.

## How Can I Fetch Data?

1. Go to [edamam.com](https://www.edamam.com/)
2. Choose `Recipe Search API` and click on `Sign Up`
3. Choose `Developer` and click on `Start Now`
4. Fill out the form.
5. Go to `Dashboard`
6. Click on `Applications` > `View`. You should see your Application ID and Application Keys on this page.
7. Copy your keys and paste them inside the code.
8. API can give some errors, if you see any **CORS errors**, add a cors browser extension for the browser you are using. [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cors-everywhere/) / [Chrome](https://chrome.google.com/webstore/detail/allow-cors-access-control/lhobafahddgcelffkeicbaginigeejlf/related)
9. Still, there is a problem? You need to wait until your API keys are available. Also, for the free version, we can only make 5 requests per minute. You can check out the [documentation.](https://developer.edamam.com/edamam-docs-recipe-api)

```js
// src/components/FoodRecipe.js
import React, {useEffect} from 'react';

const FoodRecipe = () => {
  // paste your APP_ID
  const APP_ID = '';
  // paste your APP_KEY
  const APP_KEY = '';

// url query is making a search for 'chicken' recipe
  const url = `https://api.edamam.com/search?q=chicken&app_id=${APP_ID}&app_key=${APP_KEY}`;

  // useEffect to make our API request
  useEffect(() => {
    getData();
  }, []);

  // created an async function to be able to fetch our data
  const getData = async (e) => {
    const response = await fetch(url);
    const result = await response.json();
    // console log the results we get from the api
    console.log(result);
  };

  return (
    <div>
      <h1>Food Recipe App </h1>
      <form>
        <input type="text" placeholder="Search for recipes" />
        <button type="submit" className="btn">
          Search
        </button>
      </form>
    </div>
  );
};

export default FoodRecipe;
```

![food-demo.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1609196854076/MTEG97V5s.gif)

Let's see what we did in our code:

- Created some JSX elements(form, input, and button properties).
- We are calling our function to fetch our data.
- Created a `fetch` request to get our data, and used `useEffect` hook to call our function. We are using our empty dependency array because we will only make a request when our app loads. 

We got our API response, and we got a lot of information. You can see from the gif. Now, we need to create a state for our recipes, and we will update the recipes with the API data. We will only extract `hits` and their contents from our response. Let's do it!

```js
// src/components/FoodRecipe.js
import React, {useState, useEffect} from 'react';

const FoodRecipe = () => {
  // state for our API data
  const [recipes, setRecipes] = useState([]);

  const APP_ID = '';
  const APP_KEY = '';

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

  useEffect(() => {
    getData();
  }, []);

  const getData = async () => {
    const response = await fetch(url);
    const result = await response.json();
    console.log(result);
    // set the state for our results and extract the 'hits' data from API response
    setRecipes(result.hits);
  };

  // some ui
};

export default FoodRecipe;
```

Okay, here we have added our `recipes` state and updated with `setRecipes`. From our API call, we see that `hits` is an array, that's why for the default value we put an empty array.


We need to display our recipes, for that let's create a `Recipe` component.

Go to `src > components`, create a new component, and name it `Recipe.js`. Copy this code, this will allow us to display individual recipes.

Here, I have used some Semantic UI components to display our individual recipes.

```js
// src/components/Recipe.js
import React from 'react';

const Recipe = () => {
	return (
      <div class="ui column grid">
        <div className="column recipe">
          <div className="content">
            <h2>Label</h2>
            <p>Calories: </p>
            <ul>
              <li>Ingredients</li>
            </ul>
            <a href="" target="_blank">
              URL
            </a>
            </div>
          <div className="ui fluid card">
            <img />
          </div>
        </div>
      </div>
	);
};

export default Recipe;
```

Now, we need to map over our recipes state, and display the results.

```js
// src/components/FoodRecipe.js
// ..............
return (
    <div>
      <h1>Food Recipe App </h1>
      <form>
          <input type="text" placeholder="Search for recipes" />
          <button type="submit" className="btn">
            Search
          </button>
        </form>
        <div className="recipes">
          {/* map over our array and pass our data from API*/}
          {recipes !== [] &&
              recipes.map((recipe) => (
              <Recipe
                key={recipe.recipe.url}
                label={recipe.recipe.label}
                calories={recipe.recipe.calories}
                image={recipe.recipe.image}
                url={recipe.recipe.url}
                ingredients={recipe.recipe.ingredients}
              />
            ))}
        </div>
      </div>
  );
```

For now, I am getting our `Recipe.js` without any props, of course. 


![food.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1609196898263/C8_eI_9dD.gif)

Now, we can go to our `Recipe` component and pass our props to it. We are getting these props from the parent `FoodRecipe.js`. We will use destructuring to get our props.

```js
// src/components/Recipe.js
import React from 'react';

// destructure label, calories etc
const Recipe = ({label, calories, image, url, ingredients}) => {
  return (
      <div class="ui column grid">
          <div className="column recipe">
            <div className="content">
              <h2>{label}</h2>
              <p>{calories}</p>
              <ul>{ingredients.map((ingredient) => 
                  <li key={ingredient.text}>{ingredient.text}</li>)}
              </ul>
              <a href={url} target="_blank">
                URL
              </a>
            </div>
            <div className="ui fluid card">
              <img src={image} alt={label} />
            </div>
          </div>
        </div>
  );
};

export default Recipe;
```

![chicken.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1609196940310/xqeVVFeF_.gif)

Tadaa!! We got our chickens! 

Now, we need to use our search bar, we will search the recipe from our input field. To get the state of our search bar, we will create a new piece of state.

Go to `FoodRecipe.js` and add a new `search` state.

```js
// src/components/FoodRecipe.js
// create a state for search query
const [search, setSearch] = useState('');
```

Set the value for input value `search`, `setSearch` will update our input with the `onChange` event handler. 

The `input` is keeping track of its state with the `search` state. We can get input's value from `event.target.value`.
Then we can change our state with `setSearch` function.

```js
// src/components/FoodRecipe.js
<input
  type="text"
  value={search}
  onChange={(event) => setSearch(event.target.value)}
/>
```

We need to update our state after we click on `Search Button`. That's why we need another state. And we can update our `url` from chicken query to any query. Make a new state, name it `query`.


```js
// src/components/FoodRecipe.js

const [query, setQuery] = useState('');

// when you send the form, we call onSubmit handler to query the results
const onSubmit = (e) => {
  // prevent browser refresh
  e.preventDefault();
  // setQuery for the finished search recipe
  setQuery(search);
};
```

Now, we need to pass our `query` state to our `onEffect` dependency array. Whenever we click on the Search button, we will call our API and change our state to a new `query` state.

The `query` will only run after the form submit. Use it as a dependency inside the array. Our final code now looks like this:

```js
// src/component/FoodRecipe.js
import React, {useState, useEffect} from 'react';
import Recipe from './Recipe';

const FoodRecipe = () => {
  const [recipes, setRecipes] = useState([]);
  const [search, setSearch] = useState('');
  const [query, setQuery] = useState('');

  const APP_ID = '';
  const APP_KEY = '';

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

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

  const getData = async () => {
    const response = await fetch(url);
    const result = await response.json();
    setRecipes(result.hits);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    setQuery(search);
    // empty the input field after making search
    setSearch('');
  };

  return (
    <div>
      <h1>Food Recipe App </h1>
      <form onSubmit={onSubmit}>
        <input
          type="text"
          placeholder="Search for recipes"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
        />
        <button type="submit" className="btn">
          Search
        </button>
      </form>
      <div className="ui two column grid">
        {recipes !== [] &&
          recipes.map((recipe) => (
            <Recipe
              key={recipe.recipe.url}
              label={recipe.recipe.label}
              calories={recipe.recipe.calories}
              image={recipe.recipe.image}
              url={recipe.recipe.url}
              ingredients={recipe.recipe.ingredients}
            />
          ))}
      </div>
    </div>
  );
};

export default FoodRecipe;
```


![Screen Shot 2020-12-28 at 3.14.09 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1609197279471/rNW94uegQ.png)
Time to enjoy your ice creams! I hope you liked the project.

## Wrapping Up

Now, go build something amazing, but don't pressure yourself. You can always go back to the tutorial and check how it is done, also check the [official React documentation](https://reactjs.org/docs/hooks-reference.html). Start small, try creating components first, then try to make it bigger and bigger. I hope you enjoyed this tutorial. I'm looking forward to seeing your feedback. 

If you run into any issues with your app or you have questions, please reach out to me on [Twitter](https://twitter.com/hulyakarakayaa) or [Github](https://github.com/hulyak).

### Credits:

[Giphy](https://giphy.com/)

### References:

Here are the references I used for this tutorial:

- [React Js Documentation](https://reactjs.org/docs/hooks-reference.html)
- [Overreacted A Complete Guide to useEffect](https://overreacted.io/a-complete-guide-to-useeffect/)
- [Digital Ocean's How To Build a React-To-Do App with React Hooks](https://www.digitalocean.com/community/tutorials/how-to-build-a-react-to-do-app-with-react-hooks)
- [Tutorial Example](https://caabernathy.github.io/rust-tutorial/docs/)
- [Tania Rascia's React Tutorial](https://www.taniarascia.com/getting-started-with-react/)
- [Software on the Road/React Hooks: everything you need to know!](https://softwareontheroad.com/react-hooks/#use-effect)
- [Upmostly tutorials/Simplifying React State and the useState Hook](https://upmostly.com/tutorials/simplifying-react-state-and-the-usestate-hook)
- [SitePoint/React Hooks: How to Get Started & Build Your Own](https://www.sitepoint.com/react-hooks/)

Thanks for your time. Like this post? Consider [buying me a coffee](https://www.buymeacoffee.com/hulya) to support me writing more.