---
# System prepended metadata

title: react skill
tags: [react]

---

# react skill

## useState with callBack Function  vs initialValue
```typescript
  const [count, setCount] = useState(0)
  const [count2, setCount2] = useState(0)
  return (
    <>
      <button
        onClick={() => {
      // 每次只會加 1 ，因為 react 會根據 count 的值去加
          setCount(count + 1)
          setCount(count + 1)
          setCount(count + 1)
        }}
      >add count</button>
      <button
        onClick={() => {
            // 每次加 3
          setCount2(pre => pre + 1)
          setCount2(pre => pre + 1)
          setCount2(pre => pre + 1)
        }}
      >add count2</button>
      <div>count2 {count2}</div>
      <div>count {count}</div>
    </>
)
```

因為 `count` 永遠都是 0 ，所以對 react 來說在 batch 時候只會是 `0 + 1` 的結果，所以 `count` 只會加一 1 兒不是加三。
```typescript
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
```
但 `callBack` 則是不一樣，會是 `0 + 1` `1 + 1` `2 + 1`
```typescript
setCount2(pre => pre + 1)
setCount2(pre => pre + 1)
setCount2(pre => pre + 1)
```
## useEffect clean function觸發時間

```typescript=

useEffect(()=>{
   return ()=>{
       // clean up function
   } 
},[])
```
clean function執行的時機點有二(React 18)
1. state change
元件更新 → DOM 節點改變 → 畫面渲染 → 執行cleanup 函數 → 執行 Effect
2. unmount
元件更新 → DOM 節點改變 → 畫面渲染 → 執行cleanup 函數 

備註： 
    react 17 以前 cleanup 函數是在畫面渲染前執行
    
1. state change
元件更新 → DOM 節點改變 → 執行cleanup 函數 → 畫面渲染 → 執行 Effect
2. unmount
元件更新 → DOM 節點改變 → 執行cleanup 函數 → 畫面渲染 


## 什麼是Suspense

![](https://hackmd.io/_uploads/HJ9sJTWI3.png)


* Suspense 搭配React.lazy去做Code Splitting
* 當react render 到 profilePage時如果頁面未準備好，react會先暫停這個render profilePage 這時 react.lazy會先 throw Promise-like（Thenable），並render fallback UI，直到這個component準備好時才渲染。

### stream 架構中 suspense 處理
在 stream 架構中 server 端可以搭配 client 端的 suspense boundaries 去控制 error ，值得注意的是，如果你在 server 端發生 error 其實並不會去終止 server 端的渲染，取而代之的是，會將 suspense 的 fallBack UI 例如 loading spinner 在你的 initional html 中，但這的情況會導致一個問題是這樣的suspense結果會導致 client 端不會去觸發 error boundary的內容，原因是對 client 端來說他接收到的是一個 successfully 的 suspense 結果。

![](https://hackmd.io/_uploads/B1IB0uqoh.png)

這樣的現象就變成 server 端造成的 error ，client端只會一直渲染 loading UI，而不是 error boundary 的 fallback 結果，為了解決這個問題，react 官方提供一個解法：

```typescript
<Suspense fallback={<Loading />}>
  <Chat />
</Suspense>

function Chat() {
  if (typeof window === 'undefined') {
    throw Error('Chat should only render on the client.');
  }
  // ...
}
```

這樣你的 suspense 處理就會讓 client端去做，server 端就會跳過。

但這樣你的 `error 在 server side` 跟 `client side` 的 `error log` 會太多
### Server-side error:
```typescript
Error: Chat should only render on the client.",
        "at Chat (/Users/Chat.tsx:86:19)
```
### Client-side error:

```typescript
Uncaught Error: Chat should only render on the client.
    at updateDehydratedSuspenseComponent (react-dom.development.js:20662:1)
    at updateSuspenseComponent (react-dom.development.js:20362:1)
    at beginWork (react-dom.development.js:21624:1)
    at beginWork$1 (react-dom.development.js:27426:1)
    at performUnitOfWork (react-dom.development.js:26557:1)
    at workLoopSync (react-dom.development.js:26466:1)
    at renderRootSync (react-dom.development.js:26434:1)
    at performConcurrentWorkOnRoot (react-dom.development.js:25738:1)
    at workLoop (scheduler.development.js:266:1)
    at flushWork (scheduler.development.js:239:1)
```

所以比較適合的寫法是透過 `useEffect` 的執行點判斷渲染的 `layout`
```typescript

const [client, setClient] = useState(false)

  useEffect(() => {
    setClient(true)
  }, [])

  if (!client) return null

  return <Chat />
}


```
## 什麼是Hydration

* server side render 會先初始載入inital html，但這時候頁面還沒有辦法互動，react目前無法控制dom 節點，而Hydration 就是將 event listener等等的js綁到client上。


## 自動批次處理（Automatic Batching）


什麼事 batch
```typescript
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
	const handleButtonClick = () => {
        // 第一次呼叫時，將「取代為 1」這個動作加到待執行佇列中，
        // 但還沒開始 re-render 也還沒真正更新 state 的值
		setCount(1); 

        // 第二次呼叫時，將「取代為 2」這個動作加到待執行佇列中，
        // 但還沒開始 re-render 也還沒真正更新 state 的值
        setCount(2); 

        // 第三次呼叫時，將「取代為 3」這個動作加到待執行佇列中，
        // 但還沒開始 re-render 也還沒真正更新 state 的值
        setCount(3); 

		// 執行到這裡時，這個事件 callback 已經沒有後續的事情需要處理了，
        // 此時就會開始統一進行一次 re-render，
		// 並且依序試算 count state 的待執行佇列的結果：原值 => 取代為 1 => 取代為 2 => 取代為 3
		// 因此最後會將 count state 的值直接更新成 3
	};

  // ...
}
```
當你執行 handleButtonClick 實際上 react 並不會直接修改 state 而是透過任務駐守方式執行，最後再統一 render 這叫做 automatic batching

![](https://hackmd.io/_uploads/By387KKO2.png)

## React <= 17
React 版本 <= 17 時也是有 batch 的但是是在同步情況，非同步不會。

```typescript
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  function handleClick() {
    fetchSomething().then(() => {
      // React 17 以及更早的版本不會 batch 這些
      setCount(c => c + 1); // 造成一次 re-render
      setFlag(f => !f); // 造成一次 re-render
    });
  }

  function handleClick() {
    setCount(c => c + 1);
    setFlag(f => !f);
    // React 只會 re-render 一次
  }
}

```
但如果是 setTimeout 會因為 eventLoop 關係造成多次 render 情況。
```typescript
setTimeout(
  () => {
    setCount(1);
    setCount(2);
    setCount(3);

    // 此時會為了上面的三次 setState 分別依序進行 re-render，共三次 re-render，
	// 無法自動支援 batch update
  },
  1000
);
```
甚至如果是 promise function react 在 useEffect 中會有 undefinded 問題
```typescript

export default function App() {
  const [name, setname] = useState();
  const [address, setaddress] = useState();
  useEffect(() => {
    console.log("useEffect run" + name, address);
  }, [name, address]);
  const handleCLick = () => {
    setname("Leanne Graham");
    setaddress({
      street: "Kulas Light",
      suite: "Apt. 556",
      city: "Gwenborough",
      zipcode: "92998-3874"
    });
  };
  const asynchandleCLick = async () => {
    const data = await fetch(
      "https://jsonplaceholder.typicode.com/users/1"
    ).then((res) => res.json());
    console.log(data);
    setname(data.name);
    setaddress(data.address);
  };
  const reset = () => {
    setname();
    setaddress();
  };
  return (
    <div className="App">
      <p> name :{name}</p>
      <p> address :{JSON.stringify(address, null, 2)}</p>
      <button onClick={handleCLick}>click</button>
      <button onClick={asynchandleCLick}>click async</button>
      <button onClick={reset}>reset</button>
    </div>
  );
}
```
先試試看 `handleCLick` 一切都如預期，有自動 `batch`
![](https://hackmd.io/_uploads/HyfRLICxT.png)

但當我們執行 `asynchandleCLick` 時候 `useEffect` 第一次 console.log  的 `address` 是 `undefinded` 儘管我們 `fetch api` 有資料，然後 `useEffect` 又會在 `console.log` 一次這時 `address` 才會是有資料的，顯然非同步 `update state`會造成多次的 render 情況，這樣就沒有 `auto bactch`，而且也會造成不必要的 `render` 問題 。

![](https://hackmd.io/_uploads/S1ECEUReT.png)

## 解決方式 ( react 17 )

可以試著把多個 `state` 整合到一個 `useState` 中。
```typescript
const [userInfo,setUserInfo]=useState({
    name:'',
    address:{}
})

```

或是在 `react17` 可以用 `unstable_batchedUpdates` 把 `update state`  包進去 `callback function` ˋ中。
```typescript
import { unstable_batchedUpdates } from 'react-dom'

  const asynchandleCLick = async () => {
     unstable_batchedUpdates(()=>{
         
    const data = await fetch(
      "https://jsonplaceholder.typicode.com/users/1"
    ).then((res) => res.json());
    console.log(data);
    setname(data.name);
    setaddress(data.address);
     })
     // React 只會 re-render 一次
         
  };

```

## REACT 18 

但如果是 `18` 一切都沒有問題了
```typescript
function handleClick() {
  fetchSomething().then(() => {

    setCount(c => c + 1);
    setFlag(f => !f); 
  });
     // React 只會 re-render 一次
}

setTimeout(
  () => {
    setCount(1);
    setCount(2);
    setCount(3);
    // 此時 React 會以 3 作為 setCount 的更新結果，只進行一次 re-render
  },
  1000
);

function App() {
  const [count, setCount] = useState(0);
	const [name, setName] = useState('Zet');

  const handleClick = () => {
	setCount(1);
    setName('Foo');
	setName('Bar');
    setCount(2);
    setCount(3);

    // 以上的多次且混用的 setState 呼叫 總共只會導致觸發一次 re-render：
    // 以 3 作為 count 的最後更新結果，且同時以 'Bar' 作為 name 的最後更新結果
  };
    
    // promise 也會 auto batching
  const asynchandleCLick = async () => {
    const data = await fetch(
      "https://jsonplaceholder.typicode.com/users/1"
    ).then((res) => res.json());
    console.log(data);
    setname(data.name);
    setaddress(data.address);
  };

  // ...
}

```
react 18解決無論在 timeout 、promise、native event handler 都會被Automatic Batching

https://chentsulin.medium.com/react-%E7%9A%84%E6%9C%AA%E4%BE%86-18-%E4%BB%A5%E5%8F%8A%E5%9C%A8%E9%82%A3%E4%B9%8B%E5%BE%8C-d5764e258deb


### 如果你不想 batch update 呢？

可以透過 flushSync 強制執行 react render 
```typescript
// React 版本 >= 18
import { flushSync } from 'react-dom'; // 注意：是從 react-dom 裡 import，而不是 react

function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Zet');
    
  const willRenderThreeTime = () => {
    flushSync(() => {
      setCount(1)
    })
      // re render 一次
    flushSync(() => {
      setCount(2)
    })
    // re render 一次
      
    flushSync(() => {
      setCount(3)
    })
      
      // 總共re render 三次
  }
  const onlyRenderOnce = () => {
    setCount(1)
    setCount(2)
    setCount(3)
    setCount(pre => pre + 1)
    setName(Math.random().toString())
    setCount(pre => pre + 1)
  }


  // ...
}
```

## 新 Streaming 架構 Server-Side Rendering
![](https://hackmd.io/_uploads/rJP0Z6b8n.png)

以前ssr不支援 Suspense 造成 render hydration都要一步到位。也就是waterfall 現象，導致UIUX體驗不好。\
但新的Streaming架構，就可以使用Suspense，就不必等待緩慢的component載入好後才能渲染整個頁面，前端也可包含Spinner的html，透過Chunked Transfer 轉換用 inline script 把   spinner 替換掉 Comments 在page中render的位置
![](https://hackmd.io/_uploads/HykcmpZLn.png)


## Selective Hydration 
跟前面提到的 waterfall ，以前Hydration 都要一步到位，在react 18 Selective Hydration 可以結合Suspense，
* 因為Comments還在準備inline script中視loading UI
* 這時 react 可以先處理NavBar等前面的的component的Hydration部分
* Comments準備好後將inline script中的loading UI 改成Comments，在做一次Hydration 

Selective Hydration 可以大大提升 UIUX 的效能，大大減少waterfall問題，改善fcp 時間跟ttl的提前互動。



![](https://hackmd.io/_uploads/HyFJH6bL3.png)

## Different Hydration Strategies

Partial Hydration
Partial Hydration 就是只 hydrate 需要互動的 components，其他靜態的部分就沒有必要去 hydrate。

Progressive Hydration
Progressive Hydration 則是 需要的時候才 hydrate，例如在 onClick, onFocus 或者元件出現在 viewport 之內。


### Concurrent feature

#### Transition

在整個 page 中不同的 component 彼此都有各自 render cost的時間，透過 react Transition 機制可以為這些 render去做排序，讓 high priority 的 component優先處理，減少畫面卡頓的狀況。

![](https://hackmd.io/_uploads/BJfm3y6F2.png)

![](https://hackmd.io/_uploads/ryFm3kpK2.png)

所有放到 startTransition 的 state update 都是低優先級的 component，同時可以加配 useTransition 提供的 callback 有 spinner效果

![](https://hackmd.io/_uploads/SJ0gp1TF2.png)




### 範例

#### before
例如以下的 onChange,因為auto batch的原因，所以 render 會等 setList 完成後才會觸發下一次的 render也就是 setFilter，畫面就會導致很卡，這時你可以使用 useTransition 解決這件事情。
```typescript

const [filter,setFilter]=useState()
const [list,setList]=useState()

let limit = 100000
const handleChange= (e)=>{
    setFilter(e.targe.value)
    const l = []
    for(let i=0;i<=limit,i++){
        l.push(e.target.value)
    }
    setList(l)
}

```
#### after

用 startTransition 包起來的 function 代表這個 state change的 priority 是比較低，所以 setList 會被 react 排到下一次的 render，所以 input 在頁面 onChange 變化就不會因為 setList 而 blocking the UI.，但值得注意的是 useTransition 不要亂用，原因是startTransition 是告訴 react 你要多做一次 render 的動作，所以在使用 useTransition 要記得評估多做一次的render是否可以解決 render卡頓問題，否則反而會增加效能負擔。
```typescript
const [isPending, startTransition] = useTransition()
const [filter,setFilter]=useState()
const [list,setList]=useState()

let limit = 100000
const handleChange= (e)=>{
    setFilter(e.targe.value)
    startTransition(()=>{
    const l = []
    for(let i=0;i<=limit,i++){
        l.push(e.target.value)
    }
    setList(l)
    })
        
}
```
## stream render
在 `server component` 中可以透過 `Suspense` 的 `fallback` 做 `loading`的顯示。
```typescript

import React from 'react'

const waitFor = () => new Promise((r) => setTimeout(r, 2000))
async function SuspenseCompoment() {
  await waitFor()
  return (
    <div>Test</div>
  )
}

export default SuspenseCompoment


import React, { Suspense } from 'react'

import SuspenseCompoment from './components/SuspenseCompoment'

async function ConversationsIdPage() {

  return (
    <Suspense fallback="loadig...">
      {/* @ts-expect-error Server Component */}
      <SuspenseCompoment />
    </Suspense>
  )
}

export default ConversationsIdPage

```

值得一提的是 `Next13` 中的 `page.tsx` 預設有包一層 `suspense` 然後 `loading` 可以透過添加 `loading.tsx` 顯示。

![](https://hackmd.io/_uploads/SJiIOaiZa.png)


想分享一下最近面試 senior react 遇到的考題分享給大家～
1.setStae 是非同步還是同步。
2.React 18 的渲染流程怎麼觸發。
3.useEffect 裏面 callback 跟 clear function 的執行時間。
4.請解釋 suspense。
5.什麼事 batch update。
6.fiber 是什麼
7.什麼事 immuable state。
8.vue 跟 react 的 vdom 差異是什麼


## Don’t call Hooks inside loops, conditions, or nested functions.

因為 `loops` 、 `conditions` 、`nested function` 都有可能會造成 `react` 前後次的 `hook` 執行順序或數量不一致的問題，導致觸發 `Render more hooks then previous render ` 發生。

![螢幕截圖 2023-11-22 09.59.55](https://hackmd.io/_uploads/SyEnXkjVT.png)
![螢幕截圖 2023-11-22 09.59.44](https://hackmd.io/_uploads/ryVnm1sN6.png)
![螢幕截圖 2023-11-22 09.59.31](https://hackmd.io/_uploads/ryEh7koE6.png)

[來源](https://www.youtube.com/watch?v=o_7wgRHBzeA&t=60s)
## react render 時機點 （不考慮 StrictMode）

1.  第一次渲染 : adeb
2.  點擊 click :  adecb
3.  unmount :  c
```typescript
import React, { useEffect, useState } from 'react'

export const Counter = () => {
  const [count, setCount] = useState<number>(0)
  console.log('a')
  useEffect(() => {
    console.log('b')
    return () => {

      console.log('c')
    }
  }, [count])
  console.log('d')
  return (
    <div>
      {console.log('e')}
      <button onClick={() => setCount(count + 1)}>count</button>
    </div>
  )
}


```
## 重點 : 

1. 初次渲染不執行 `clean function`
2. `state change` `useEffect` 執行順序 `clean function` 然後 `callback function` 
3. `unmount` 只會執行 `clean function`
4. 每次 `render` 整個 `component` 都會 `console.log` 


## Don't call react function component
https://kentcdodds.com/blog/dont-call-a-react-function-component

## hook
```typescript
import { useCallback, useEffect, useRef, useState } from 'react'
import { useCounter } from './useCounter'

interface UseTimerProps {
  onTime: () => void
  time: number
  delay?: number
}
export const useTimer = ({ time, onTime, delay = 1000 }: UseTimerProps) => {
  const callBackRef = useRef<() => void>()
  const timerIdRef = useRef<NodeJS.Timeout>()
  const { count, increment, reset } = useCounter(0)

  useEffect(() => {
    callBackRef.current = onTime
  }, [onTime])

  useEffect(() => {
    timerIdRef.current = setInterval(() => {
      if (count === time / 1000) {
        callBackRef.current()
        clearInterval(timerIdRef.current)
      } else {
        increment()
      }
    }, delay)

    return () => {
      clearInterval(timerIdRef.current)
    }
  }, [count, delay, increment, time])
  return { count, reset }
}


const useCounter = (initionalValue: number) => {
  const [count, setCount] = useState(initionalValue)
  const increment = useCallback(() => setCount(pre => pre + 1), [])
  const decrement = useCallback(() => setCount(pre => pre - 1), [])
  const reset = useCallback(() => setCount(initionalValue), [])
  return {
    count,
    increment,
    decrement,
    reset,
  }
}
```

## useEffect vs useLayoutEffect



|          | useEffect| useLayoutEffect|
| -------- | -------- | -------- |
| 執行時間     | render 後執行     | render 前執行     |
|     async / sync | async     | sync    |
| 可否用在 ssr 中    | yes    | no    |


```typescript
import { useEffect, useLayoutEffect, useState } from 'react'


function App() {
  const [isAdmin, setIsAdmin] = useState<boolean>(false)
  const [useId, setuseId] = useState<number>(0)
  const now = performance.now()
  while (performance.now() - now < 300) { }
//     這邊把 useEffect 替換成 useLayoutEffect 會有不一樣的效果
  useEffect(() => {
    setIsAdmin(useId === 1)
  }, [useId])
  const handleChangeUser = () => {
    const anotherUserId = 1
    setuseId(anotherUserId)
  }
  return (
    <>
      <p>useEffect</p>
      <p>useId: {useId}</p>
      <p>isAdmin: {isAdmin ? 'true' : 'false'}</p>
      <button onClick={handleChangeUser}>changeUser</button>
    </>
  )
}

export default App

```
因為 `useEffect` 他是 `non-block state update` 所以你會發現儘管 `userId` 改變了，`isAdmin` 並沒有立即渲染對應的結果，原因是 `useEffect` 的 `non-block` 特性不會等所有 `state update` 後才 `render` 完整的 `UI`，再加上 `component` 中的大量運算，會導致 `useEffect update state` 會有時間差 ，這可能會造成 `user` 畫面上的困惑。
![ezgif.com-video-to-gif (3)](https://hackmd.io/_uploads/B1CzZ9ZU6.gif)

取而代之你可以使用 `useLayoutEffect` ，他會等所有的 `state` 都 `update` 後才會 `render UI`，但你會發現 `UI` 結果會等一段時間後才會出現，但解決了 `state` 同步的需求。

![ezgif.com-video-to-gif (2)](https://hackmd.io/_uploads/S1AzZcWLp.gif)

#### 總結
所以如果你需要同步 `state` 在渲染 `UI` 的話可以使用 `useLayoutEffect` 但切記大多數情況下不太會去用它，可能會有額外的效能問題，如果你的 `component` 很肥大，使用 `useLayoutEffect` 的 `renter` 結果就會更久，所以謹慎使用～

### dom 操作的行為放到 useLayoutEffect 中

原因很簡單因為 `useLayoutEffect` 會在 `render` 前執行，可以可以確保 `layoutout` 不會跑版或是白畫面問題。

## SSR 不支援 useLayoutEffect

在 `useLayoutEffect`  因為不會在 `SSR` 執行所以可能會有非預期的 `hydration` 狀況發生，所以勁量避免。
```typescript
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.
```

解決方式就是使用 `customerEffect`

```typescript
import { useEffect, useLayoutEffect } from 'react';
const customerEffect = typeof window !== 'undefined' ?useLayoutEffect:useEffect
```

## react 18 useEffect mount 兩次


```typescript
 useEffect(() => {
    const getPost = async () => {
      try {
        const data = await fetch('https://jsonplaceholder.typicode.com/todos/1')
      } catch (e) {
        console.log(e)
      }
    }
    getPost()
    return () => {
      // abortController.abort()
    }
  }, [])
```

在 react 18 中你會發現 `useEffect` 裡頭的 `request`  被執行兩次

![截圖 2023-12-09 下午3.49.24](https://hackmd.io/_uploads/BJJm25ZLT.png)

原因其實是 `StrictMode` 關係，`useEffect` 會被 `remount` 兩次，官方是說 `StrictMode` 只會在 `dev` 中發生，`prod` 不會影響到，但這樣儘管是在 `dev` 中還是會有不必要的 `request` 問題發生。
```
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
      <App />
  </React.StrictMode>,
)

```
### 解決方式
使用 `AbortController` 在 `unmount` 時 `abort`
```typescript
 useEffect(() => {
    const abortController = new AbortController()
    const getPost = async () => {
      try {
        const data = await fetch('https://jsonplaceholder.typicode.com/todos/1', { signal: abortController.signal })
      } catch (e) {
        console.log(e)
      }
    }
    getPost()
    return () => {
      abortController.abort()
    }
  }, [])
```
如此一來就可以減少 `request` 浪費摟～
![截圖 2023-12-09 下午3.53.37](https://hackmd.io/_uploads/S1zzT9bLa.png)

## useSyncExternalStore reSubscrible
`useSyncExternalStore` 如果需要把 `subscribe function` 放到 `component` 中時記得要包 `useCallBack` 否則每次 `component rerender` 時都會重新 `re subscribe`  ，容易造成效能問題。

```typescript
function ChatIndicator() {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot);
  
  // 🚩 Always a different function, so React will resubscribe on every re-render
  function subscribe() {
    // ...
  }

  // ...
}
```
解決方式就是加上 `useCallBack` 

```typescript
function ChatIndicator({ userId }) {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot);
  
  // ✅ Same function as long as userId doesn't change
  const subscribe = useCallback(() => {
    // ...
  }, [userId]);

  // ...
}
```
或是只在 `hook`中使用 `useSyncExternalStore`

```typescript
export function useOnlineStatus() {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
  return isOnline;
}

function getSnapshot() {
  return navigator.onLine;
}

function getServerSnapshot() {
  return true; // Always show "Online" for server-generated HTML
}

function subscribe(callback) {
  // ...
}
```

## get Element props to component
```typescript
import React from 'react'
interface CardProps extends React.HTMLAttributes<HTMLElement> { }
export const Card = (props: CardProps) => {
  return (
    <div {...props}>AAA</div>
  )
}


```

## Adjusting some state when a props changes

有時候我們可能會需要  `rest component`  中的 `state` ，在每次 `render` 的時候，需求會是每次 `update select` `comment` 會需要全部 `reset` 。
![截圖 2024-01-06 下午2.00.14](https://hackmd.io/_uploads/S14B6w8uT.png)


```typescript
function App() {
  const [userId, setUerId] = useState<string>('')
  return (
    <>
      <select name="" id="" onChange={e => setUerId(e.target.value)}>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <Profile userId={userId} />
    </>
  )
}

//  ./Profile.tsx
import React, { useState } from 'react'

interface ProfileProps {
  userId: string
}
export const Profile = ({
  userId
}: ProfileProps) => {
  const [comment, setComment] = useState<string>('')
    console.log('Profile render')
  return (
    <>
      <div>userId {userId}</div>
      <p>comment : {comment}</p>
      <input type="text" value={comment} onChange={e => setComment(e.target.value)} />
    </>
  )
}
```
但上面的 `code` 會有問題，儘管我們 `userId change` 觸發 `react render` 但我們的  `comment` 還是保留最後一次的內容沒有 `reset`。
![ezgif.com-video-to-gif-converter](https://hackmd.io/_uploads/rkMV0vL_a.gif)
![截圖 2024-01-06 下午2.09.55](https://hackmd.io/_uploads/S1GTCvLd6.png)

正常情況下你的第一直覺可能會用 `useEffect` 的 `dependencies` 去監聽 `userId` 的變化然後 `reset comment`。
```typescript
//  ./Profile.tsx
import React, { useState } from 'react'

interface ProfileProps {
  userId: string
}
export const Profile = ({
  userId
}: ProfileProps) => {
  const [comment, setComment] = useState<string>('')
    console.log('Profile render')
    
  useEffect(() => {
    setComment('')
  }, [userId])
  return (
    <>
      <div>userId {userId}</div>
      <p>comment : {comment}</p>
      <input type="text" value={comment} onChange={e => setComment(e.target.value)} />
    </>
  )
}
```
雖然現在可以 `reset` ，但其實可能會有淺在的效能問題。
![ezgif.com-video-to-gif-converter](https://hackmd.io/_uploads/BJXcJdU_6.gif)

#### you might not need useEffect

使用 `useEffect` 會重新觸發 `react rerender` ，也就是你的 `dom` 節點將會傳不重新 `calc` 與排序，重新跑一次 `reconciliation` ，這樣帶來的 `side effect` 會是如果你的 `Profile` 節點太肥，會因為 `reset` 關係導致所有 `dom` 都需要重新計算，效能會有問題。

解決方式就是把 `useEffect` 拿掉改用 `state` 如下：

透過 `preState` 的方式，去做 `reset` ，一方面是實現 `useEfect` 中呼叫`callBack` 的方式，同時也不會有效能上 `side Effect` 的問題。
```typescript
interface ProfileProps {
  userId: string
}
export const Profile = ({
  userId
}: ProfileProps) => {
  const [comment, setComment] = useState<string>('')
  const [preItems, setPreItems] = useState(userId)
  if (preItems !== userId) {
    setPreItems(userId)
    setComment('')
  }
  return (
    <>
      <div>userId {userId}</div>
      <p>comment : {comment}</p>
      <input type="text" value={comment} onChange={e => setComment(e.target.value)} />
    </>
  )
}
```

但這樣可能還不是最佳解如果你的 `props` 更多那是不是也要用更多的 `preState` 去判斷，這時你可以考慮用 `key` 的方式。

```typescript
function App() {
  const [userId, setUerId] = useState<string>('')
  return (
    <>
      //...
      <Profile userId={userId} key={userId} />
    </>
  )
}
```
同時 `Profile` 也不需要額外再判斷 `preState` `code` 是不是更乾淨了呢～
```typescript
import React, { useEffect, useState } from 'react'

interface ProfileProps {
  userId: string
}
export const Profile = ({
  userId
}: ProfileProps) => {
  const [comment, setComment] = useState<string>('')
  return (
    <>
      <div>userId {userId}</div>
      <p>comment : {comment}</p>
      <input type="text" value={comment} onChange={e => setComment(e.target.value)} />
    </>
  )
}

```

## useDeferredValue vs debounce
1. `useDeferredValue`  他不需要自己設定` debounce timeout` 會是根據 `user` 的當下環境自己決定 `debounce` 時間，設備好的 `debounce` 更快
1. 這個 `hook` 是直接綁定 `react` 的 `render` 生命週期
1. `useDeferredValue` 他的 `render` 是可以中斷的，而 `debounce` 則不行，什麼意思呢假設 `user` 在 `input` 輸入 3 次 ,` a->ab->abc` ，`denounce` 在 `ui` 上就會依序去 `render` ，但 `useDeferredValue` 只會 `render` 最後一次 `abc` 跟 `batch update` 很像。

[react 官網](https://react.dev/reference/react/useDeferredValue)


## use API
* 可以搭配 `context` 跟 `promise` 使用
* `use API` 可以用 `loop` 或是 `conditional statements`