ทำความเข้า useEffect ตั้งแต่เริ่มต้น
Tags: react, hook
Published: 2021-02-23 07:20:20

## บันทึกความเข้าใจจาก 

[⚛️ React ไปวันๆ EP.2 - ทำความเข้า useEffect ตั้งแต่เริ่มต้น](https://youtu.be/-zEW9tlDmdM)

## History

![Screenshot_7](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pbi6vgs4pwy5a1n9eql9.png)
 
react v16.8 มีการเอา hook เข้ามา

## Why so popular

ก่อนหน้าที่ hook เขามาเราเก็บ Logic ใน class
![Screenshot_8](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vf5tcrzm3fatcw9mho1v.png)


ก่อนหน้าที่ hook เข้ามาเราไม่สามารถเขียน state (logic)(ไม่มี life cycle) ให้อยู่ใน function component ได้

มี 2 patterns ในการ share  logic

- Hight Order Component
- Render Function

### ปัญหา

- แต่ก็มีปัญหาต่อมาคือ Hight Order component **Hell** 
มีการครอบ component หลายชั้น

- Render Function **Hell**


##  useEffect คือ?

let us run/cleanup a side-effect that synchronizes with some variables


## Hook mental model 

แต่ละการ render ของ functional component คือ 1 snapshot

## Component

- Function => symchronnization,Immutable state
- Class => Life cycle Mutable state(เปลี่ยนกระทันหันไม่ได้)
(ดูนาที 35.00)


## Cleanup pitfalls 
- cleanup ถูกเรียกครั้งเดียวเมื่อ component unmounted จาก Dom
- cleanup  ถูกเรียกเมื่อ state changed

สิ่งที่กล่าวมาข้างบนคือ **ผิด**

จริงๆ แล้วสำหรับ useEffect cleanup   ถูกเรียกทุกครั้ง


![Screenshot_9](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/08rdmz5slh54vnvkh2hq.png)
 

![Screenshot_10](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d96f02cz398bvdzylh0u.png)
 
Dom จะ painted ก่อนแล้ว clearup ถึงจะ  run


## Dendency array

We don't want the effect to run on every render


```js
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => {
      console.log("cleanup");
      clearInterval(id);
    };
  }, []);

  return <h1>{count}</h1>;
}
```
code ด้านบนมันควรจะแสดง 1,2,3,4,5,6... (นาทีที่ 7.40)
แต่มันแสดงแค่ `1` 

การทำงานมันคือ `useEffect` ทำงานแค่ครั้งเดียวแม้ว่า count จะเปลี่ยนก็ตาม

ที่นี้ลองใส่ `count` 

```js
function Counter() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => {
      console.log("cleanup");
      clearInterval(id);
    };
  }, [count]);

  return <h1>{count}</h1>;
}
```

## Type dependency array

```js
function Counter() {
  const [count, setCount] = useState(0);
  const [params, setParams] = useState({ params: "test" });

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>increase count</button>
      <Child query={[params]} />
    </div>
  );
}

function Child({ query }) {
  useEffect(() => {
    console.log("I should see this once!", query);
  }, [query]); // Object.is
  return null;
}
```

เมื่อกดปุ่ม increse cout แล้ว function Child มันดันทำงานด้วยเพราะ ทุกการ re-render คือการสร้าง object `param:"test"` ขึ้นมาใหม่ และ referrence ไม่เหมือนกัน 

### จะแก้ยังไง?

กลับไปใช้ `useCompareEffect`


 ### เจอแบบ Object มาแล้วถ้าเป็น function ละ

```js
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>increase count</button>
      <Child query={{ params: "test" }} />
    </div>
  );
}

const useDeepCompareCallback = () => {}

function Child({ query }) {
 function fetchData(){
  console.log("Api called with",query )
 }
  useEffect(() => {
    fetchData();
  }, [fetchData]); // this is correct
  return null;
}
```

function `fetchData()` ใช้ `useCompareEffect` __ไม่ได้ผล__

### วิธีแก้
 - แบบง่ายก็ย้าย `fetchData()` เข้าไปใน  `useDeepCompareEffect()`
 - เราต้องทำให้ `fetchData()` มันไม่ได้เปลี่ยนเวลา มี การ re-render
เราเลยต้องใช้ `useCallBack` (นาทีที่20)
 
```js
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>increase count</button>
      <Child query={{ params: "test" }} />
    </div>
  );
}

const useDeepCompareCallback = () => {}

function Child({ query }) {
  const fetchData = useCallback(() => {
    console.log("Api called with", query);
  }, [query]); // Object.is
  useEffect(() => {
    fetchData();
  }, [fetchData]); // this is correct
  return null;
}
```

แต่เดียวก่อน ... `useCallback()` ก็ยังจะต้องการ dependency อยู่ ไปดูวิธีแก้ที่ (ดูนาที22.28) 


## ใช้ useReducer แก้ปัญหา useState เยอะแล้วจะมั่ว

> dispatch, setState จะไม่เปลี่ยน ref เวลา re-render
```js

 const initialState = {
   count: 0,
   step: 1
 };
function Counter() {
  // dispatch, setState, ref
  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, step } = state;

  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: "tick" });
    }, 1000);
    return () => clearInterval(id);
  }, [dispatch]);

  return (
    <>
      <h1>{count}</h1>
      <input
        value={step}
        onChange={e => dispatch({ type: "step", step: Number(e.target.value) })}
      />
    </>
  );
}

function reducer(state, action) {
  const { count, step } = state;
  if (action.type === "tick") {
    return { count: count + step, step };
  } else if (action.type === "step") {
    return { count, step: action.step };
  } else {
    throw new Error();
  }
}
```


[CodeSanbox](https://codesandbox.io/s/ep003-useeffect-gist-yz2qb?file=/src/pages/dependency-array.js&fbclid=IwAR07gVmpvI5cT9_9SWPWT6H87BZUpaEN1ARfzEdto5mo-hn4JPKUmgnwjVg)