# 知識閱讀 - 5 種 useEffect 無限迴圈的模式

在 React 16 發佈後，`useEffect` 可以說是目前使用最多的 hook，因為它提供了`componentDidMount`、`componentDidUpdate` 和 `componentWillUnmount` 生命週期的組合功能。

```javascript
//          callback
//           ||
//           V
useEffect(()=>{}, [])
//                 ^
//                ||
//           dependency
```

當 dependency 改變時 useEffect 才會去 trigger callback 函數。這邊要注意的就是 dependency。因為當你沒有給予 dependency，然後又在 useEffect 裡面去更新 state 的話，就會造成無限迴圈了。

```javascript
useEffect(() => {
  fetch("/api/user")
    .then((res) => res.json)
    .then((res) => {
      setData(res);
    });
});
```

永遠要記得這句話「當 state 或 props 改變時，元件就會重新渲染」，所以當上面的程式碼中的 `setData(res)` 就會去更新 state，然後就會觸發重新渲染，但是 `useEffect` 沒有值作為 dependency，就會重新被執行，結果又會重新的更新 `setData(res)`，一直無限的重新渲染。

### 如何修正

修正的辦法可以改成以下：

```javascript
useEffect(() => {
  fetch("/api/user")
    .then((res) => res.json)
    .then((res) => {
      setData(res);
    });
}, []);  // <--- dependency
```

但某些情境下，[官方上會認為是不安全的做法](https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies)

## 函數作為 dependency

`useEffect` 使用 shallow object 來判斷是否值有改變與否，但是因為 JavaScript 本身**詭異的**比較系統，會有以下的問題

```javascript
var mark1 = function(){ return ('100')}; // has a unique object reference
var mark2 = function(){ return('100')}; // has a unique object reference
mark1 == mark2; // false
mark1 === mark2; // false
```

然後我們來看看以下的範例

```javascript
import React, { useCallback, useEffect, useState } from "react";
export default function App() {
  const [count, setCount] = useState(0);
  const getData = () => {
    return window.localStorage.getItem("token");
  }; 
  const [dep, setDep] = useState(getData());
  useEffect(() => {
    setCount(count + 1);
  }, [getData]);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}
```

我們將 `getData` 這個函數拿來當作 dependency，但是執行之後你會發現出現錯誤 `Maximum update depth exceeded`。這就是因為在 JavaScript 的系統中，`getData` 永遠都會被判斷為不相同造成的。

### 如何修正

這邊可以使用 `useCallback` 來修正此問題，因為 `useCallback` 會回傳一個 memoized 版本，只有在 dependency 改變時才改變

```javascript
const getData = useCallback(() => {
    return window.localStorage.getItem("token");
  }, []); // <- dependencies
```

## 陣列作為 dependency

因為在 shallow comparison 中陣列總是錯誤的 (`[] === [] // false`)，所以也會造成無限迴圈

```js
import React, { useCallback, useEffect, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
  const dep = ['a'];
  const [value, setValue] = useState(['b']);
  useEffect(() => {
    setValue(['c']);
  }, [dep]);
  
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}
```

### How to fix

因為 `useCallback` 會回傳函數，所以我們無法使用它來修正。這邊我們使用 `useRef`。`useRef` 回傳一個可變物件，其 `.current` 具有初始值。

```js
import React, { useEffect, useState, useRef } from 'react';
export default function Home() {
  const [value, setValue] = useState(['b']);
  const {current:a} = useRef(['a'])
  useEffect(() => {
    setValue(['c']);
  }, [a])
}
```

## 物件作為 dependency

物件跟陣列一樣的關係，所以也都會造成無限迴圈

```js
import React, { useCallback, useEffect, useState } from "react";
export default function App() {
  const [count, setCount] = useState(0);
  const data = {
    is_fetched:false
  };
  useEffect(() => {
    setCount(count + 1);
  }, [data]);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}
```

### How to fix

這邊我們使用 `useMemo` 來修正這問題。`useMemo` 只會在 dependency 發生變化時重新計算 memoized value。

```js
import React, { useMemo, useEffect, useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const data = useMemo(()=>({
    is_fetched:false
  }), []); // <- dependencies
  useEffect(() => {
    setCount(count + 1);
  }, [data]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}
```


## Reference
- [5 useEffect Infinite Loop Patterns](https://javascript.plainenglish.io/5-useeffect-infinite-loop-patterns-2dc9d45a253f)














