React hooksのuseEffectについて理解したい

# useEffectとは？

>useEffect は何をやっているのか？ このフックを使うことで、レンダー後に何かの処理をしないといけない、ということを React に伝えます。React はあなたが渡した関数を覚えており（これを「副作用（関数）」と呼ぶこととします）、DOM の更新の後にそれを呼び出します。この副作用の場合はドキュメントのタイトルをセットしていますが、データを取得したりその他何らかの命令型の API を呼び出したりすることも可能です。

https://ja.reactjs.org/docs/hooks-reference.html#useeffect:~:text=%E3%81%A6%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84%E3%80%82-,useEffect,-useEffect(didUpdate

つまりuseEffectは、Reactのレンダリング後に関数を実行することができるフックということです。

関数コンポーネント内で[副作用](https://qiita.com/Mitsuw0/items/801f783ca74b062c1ed8)の処理（DOM操作、変数の代入、API通信などUI構築以外の処理）を扱え、実行することができます。

またuseEffectは、ReactのClassコンポーネントのライフサイクル
componentDidMount, componentDidUpdate、componentWillUnmount
の3つと同様な処理を行うことができます。


# useEffectを色々試してみる

## レンダリング後にuseEffectを実行する

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

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

  useEffect(() => {
    const text = document.getElementById("text");
    if(text) {
      text.innerHTML = `useEffectの値は${(count * 3)} です`
    }
  });

  return (
    <div>
      <p>ボタンが {count} 回押されました</p>
      <button onClick={() => setCount(count + 1)}>
        ボタン
      </button>
      <p id="text"></p>
    </div>
  )
};

```

まずはレンダリング後にuseEffectを実行する方法です。

onClickされるとsetCountでcountが増えて行きます。
この際に再レンダリングされるのでuseEffectも実行され、ボタン下のpタグに増えた分のcountを3倍した値が表示されるという流れになります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392337/e903899b-9a6c-206c-2848-cdc8d7236109.png)


## 最初のマウント時だけuseEffectを実行する

useEffectの第二引数に空配列を渡します。

```
useEffect(() => { ... }, []);
```

それ以外は、さっきのコードと同じです。

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

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

  useEffect(() => {
    const text = document.getElementById("text");
    if(text) {
      text.innerHTML = `useEffectの値は${(count + 1)} です`
    }
  }, []);

  return (
    <div>
      <p>ボタンが {count} 回押されました</p>
      <button onClick={() => setCount(count + 1)}>
        ボタン
      </button>
      <p id="text"></p>
    </div>
  )
};

```

このように再レンダリングされても、useEffectでのカウントは増えません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392337/d3b4e1ca-c2be-b6e8-8d66-f9fddebba102.png)


## 値の変更時にuseEffectを実行する

値が変化したときにuseEffectを実行したい場合もあるかと思います。
そんな時は、useEffectの第二引数の配列にその値を渡すことで実現できます。

```
useEffect(() => { ... }, [変数1, 変数2, ...]);
```

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

export const UseEffectExample = () => {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  useEffect(() => {
    const text = document.getElementById("text");
    if(text) {
      text.innerHTML = `useEffectの値は${(count * 5)} です`
    }
  }, [flag]);

  return (
    <div>
      <p>ボタンが {count} 回押されました</p>
      <button onClick={() => setCount(count + 1)}>
        ボタン
      </button>
      <button onClick={() => setFlag(true)}>
        ボタン2
      </button>
      <p id="text"></p>
    </div>
  )
};

```

ボタン2はfalseの状態のflagをtrueに変えるだけのボタンです。

このボタンの押下で、flagの値が変化しuseEffectが実行されます。
二度目は、すでにflagはtrueとなっているため値が変わらずに、useEffectは実行されません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392337/aa5eb094-741f-3641-9f13-855b6bd997dd.png)

## 無限ループに気を付ける

useEffectの第二引数を指定せずにuseStateのsetterを呼び出してしまうと、
以下のようにuseEffectの処理が無限ループします。

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

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

  useEffect(() => {
    setCount(count + 1)
  });

  return (
    <div>
      <p>ボタンが {count} 回押されました</p>
      <button onClick={() => setCount(count + 1)}>
        ボタン
      </button>
      <p id="text"></p>
    </div>
  )
};

```

たった数秒でこのような状態に…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/392337/3996b32a-fdab-d20b-57d9-fa7b9ba41d0f.png)

:::note warn
一応、フォーマッターなどで以下のように警告を出してくれる場合もありますが、
あまり過信せず十分気をつけましょう。
:::

```
React Hook useEffect contains a call to 'setCount'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [count] as a second argument to the useEffect
```

## 参考
https://ja.reactjs.org/docs/hooks-effect.html

https://ja.reactjs.org/docs/hooks-reference.html#useeffect:~:text=%E3%81%A6%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84%E3%80%82-,useEffect,-useEffect(didUpdate

https://amateur-engineer.com/react-use-effect-execution-timing/