---
# System prepended metadata

title: React Concepts

---

# React Concepts
[2018發表Hook](https://www.youtube.com/watch?v=dpw9EHDh2bM)
#### React 合成事件機制
關鍵字:Batching
[谷歌分享的連結](https://github.com/reactwg/react-18/discussions/46#discussioncomment-846694)
#### 避免Anit-Pattern
幫Sharon解googleMap遇到的兩個反模式:
1. 串接多個useEffect,依賴前一個去計算,最後只用最後一個useEffect的值去render
2. 在useEffect裡用Array loop method去執行async/await 的call-back,其實是快速的丟了三個async function,處理起來並不是同步的
若想強制同步,要使用for loop(是強制同步逐行跑的概念)
但怕等很久,可以用map+promise.all折衷,解法：
```
const [latLngArr, setLatLngArr] = useState<LatLng[]>([]);
  useEffect(() => {
    const fetchDataAndSetLatLngArr = async () => {
      const barsCollectionRef = collection(db, "bars");
      const data = await getDocs(barsCollectionRef);
      const bars = data.docs.map((doc) => ({
        ...(doc.data() as IBar),
        id: doc.id,
      }));

      const address = bars.map((bar) => bar.address);
      const latLngPromises = address.map((address) => fetchData(address));
      const latLngArr = await Promise.all(latLngPromises);
      setLatLngArr(latLngArr);
    };

    fetchDataAndSetLatLngArr();
  }, []);
```
#### react針對拼音輸入法處理: [Composition Events](https://kuro.tw/posts/2016/10/11/%E7%AD%86%E8%A8%98-%E9%80%8F%E9%81%8E-Composition-Events-%E5%A2%9E%E5%BC%B7%E9%9D%9E%E6%8B%89%E4%B8%81%E8%AA%9E%E7%B3%BB%E8%BC%B8%E5%85%A5%E6%B3%95%E5%B0%8D%E8%BC%B8%E5%85%A5%E6%A1%86%E7%9A%84%E6%94%AF%E6%8F%B4/) | [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event)
#### [React Docs](https://beta.reactjs.org/learn/choosing-the-state-structure):的Learn React文章含金量很高
#### 一定要放到useEffect裡的事
可以把useEffect想像成window.eventListener
1. 打API: useEffect 是唯一能控制時機跟次數的地方
#### 有control的input
input通常是用onChange來觸發事件,若要讓input被state控制,value屬性必須跟state有關,才能用onChange去控制
#### `useState()`/`useReducer`裡放callback的
```
const [ state , setState ] = useState(()=>{產生值的function,如fetch});
```
裡面放callback funciton時react會知道只需要在第一次用這個callback.
```
const [ state , dispatch ] = useReducer(reducer,null,()=>{產生值的function,如fetch});
```
reducer的用法,要放在第三個參數,第二個參數會被變成callback的arg.
#### 第一次render要避免沒資料的狀
加一個if判斷式
```
  if (!products) {
    console.log("not data");
    return null;
    //return undefined;
    //return '';
    //return <></>;
    //return;
  }
```
#### JSX可以讀取array,所以product.map生成的JSX可以不用一個一個取出,可以包在[]裡直接return
* forEach回傳值是unedfined,所以直接用不行,但可以先宣告一個`變數=[]`,然後生成的jsx內容再push進array
* map的JSX如果沒有給key,預設會用index去給
#### 一個function不會片段執行,所以重新渲染都是整個function component重新執行
#### function Component 裡不要再宣告一個function Component,會有效能問題(因為每次render都會重新宣告裡面的component)
### Cart設計
```
function handleAddtoCart() {
        if (selectedColor !== undefined && selectedSize !== null && quantity !== 0) {
            console.log('Click ADDTOCART!!');
            const item = {
                productId: product.id,
                color: product.colors[selectedColor],
                size: product.sizes[selectedSize],
                quantity,
            };
            const cart = JSON.parse(localStorage.getItem('cart')) || [];
            const index = cart.findIndex(
                (cartItem) =>
                    cartItem.productId === item.productId &&
                    cartItem.color.code === item.color.code &&
                    cartItem.size === item.size
            );
            if (index === -1) {
                cart.push(item);
            } else {
                cart[index].quantity += item.quantity;
            }
            localStorage.setItem('cart', JSON.stringify(cart));
            console.log('Add to cart:', cart);
            const totalQuantity = cart.reduce((total, i) => total + i.quantity, 0);
            setTotalProduct(totalQuantity);
        }
    }
```
有用到local storage的功能：
```
  function addToCart() {
    if (stockLeft > 0 && amount > 0) {
      localStorage.setItem(
        new Date(),
        JSON.stringify({
          id: details.id,
          qty: amount,
          colorCode: selectColor,
          size: selectSize,
          newStock: stockLeft - amount,
        })
      );
      updateStock(selectColor, selectSize, stockLeft - amount);
      setAmount(0);
      setSelectSize(null);
      setSelectColor(null);
      setColorStock({});
    }
  }

  const updateStockViaLocal = () => {
    if (details === undefined) {
      return;
    }
    for (let i = 0; i < localStorage.length; i++) {
      const { colorCode, size, newStock } = JSON.parse(
        localStorage.getItem(localStorage.key(i))
      );
      updateStock(colorCode, size, newStock);
    }
  };

  function calcCart() {
    let sum = 0;
    for (let i = 0; i < localStorage.length; i++) {
      const { qty } = JSON.parse(localStorage.getItem(localStorage.key(i)));
      sum += qty;
    }
    return sum;
  }
  
  function showCartAmount() {
    let sum = 0;
    for (let i = 0; i < localStorage.length; i++) {
      const { qty } = JSON.parse(localStorage.getItem(localStorage.key(i)));
      sum += qty;
    }
    cartCount.forEach((cart) => (cart.textContent = sum));
  }
  
  app.js
```

### 我想在子組件A處理資料傳給父組件,再從父組件傳給子組件B使用,分別要做哪些事？
1. 在子組件A中，定義一個函數來處理資料，這個函數將被調用時將資料作為參數傳遞給父組件。
2. 在子組件A中，使用props將剛才定義的函數傳遞給父組件。
3. 在父組件中，定義一個函數來接收子組件A傳遞過來的資料，並將資料存儲在父組件的狀態中。
4. 在父組件中，使用props將狀態中的資料傳遞給子組件B。
5. 在子組件B中，使用props接收來自父組件的資料，並使用它進行相關操作。
```
//子組件A:
function ChildA(props) {
  const handleData = (data) => {
    props.onDataReceived(data);
  };

  return (
    <div>
      <button onClick={() => handleData("example data")}>Send Data</button>
    </div>
  );
}
```
```
//父組件:
function Parent() {
  const [data, setData] = useState(null);

  const onDataReceived = (data) => {
    setData(data);
  };

  return (
    <div>
      <ChildA onDataReceived={onDataReceived} />
      <ChildB data={data} />
    </div>
  );
}
```
```
//子組件B:
function ChildB(props) {
  return (
    <div>
      <p>Received data: {props.data}</p>
    </div>
  );
}
```
在這個例子中，當在子組件A中點擊"Send Data"按鈕時，將會調用handleData函數並將"example data"作為參數傳遞給父組件的onDataReceived函數，父組件接收到資料後將其存儲在狀態中。最後，子組件B使用props接收來自父組件的資料並將其渲染在頁面上。
* **狀況**: 子組件eslint會報錯,要安裝[PropTypes](https://blog.logrocket.com/validate-react-props-proptypes/),並在component檔裡設定來validat props才能解決
報錯：
![](https://i.imgur.com/WRtmDfw.png)
```
//子組件檔案
import PropTypes from 'prop-types';

function ReactComponent(props) {
  // ...implement render logic here
}

ReactComponent.propTypes = {
  // ...prop type definitions here
}
```
### 用`prevCart.id = 123`更新state的值,state為何會變123?
```
const [cart, setCart] = useState({});
setCart(prevCart => {prevCart.id = 123 return prevCart});
後面那個object才會被return覆蓋前值,不然會直接return 123
```
### 如果state是一個object,setState若只是更新同個object內的值,react會判斷state沒有更新,必須return一個新的object覆蓋舊的(reference的問題)
```
const [cart, setCart] = useState({    //state是一個object
  id: undefined,
  name: undefined,
  price: undefined,
  color: { code: undefined, name: undefined },
  size: undefined,
  qty: 0,
});

// 在修改 cart 对象时，使用扩展运算符来创建新的object(不同的reference)
const updateCart = (newCartValue) => {
  setCart({ ...cart, ...newCartValue });
};

useEffect(() => {
  const date = new Date();
  localStorage.setItem(date.toJSON(), JSON.stringify(cart));
  props.setCartQty(updateCartQty());
  console.log("cart!");
}, [cart]);
```
### 在網頁載入,並觸發setDetails改變details之後執行此function,但又不能造成無限循環,有什麼方法？
在 useEffect hook 中設置一個 flag，表示是否要執行 `updateStockViaLocal`。只有在這個 flag 為 true 時，才執行 `updateStockViaLocal`。這樣就可以防止無限迴圈了。
```
const [details, setDetails] = useState(undefined);
const [shouldUpdateStockViaLocal, setShouldUpdateStockViaLocal] = useState(false);

useEffect(() => {
  async function loadData() {
    try {
      const res = await fetch(
        `https://api.appworks-school.tw/api/1.0/products/details?id=${id}`
      );
      const json = await res.json();
      setDetails(json.data);
      setShouldUpdateStockViaLocal(true); // 設置 flag 為 true
    } catch (error) {
      console.log(error);
    }
  }
  loadData();
}, []);

useEffect(() => {
  if (shouldUpdateStockViaLocal) { // 只有當 flag 為 true 時，才執行 updateStockViaLocal
    updateStockViaLocal();
    setShouldUpdateStockViaLocal(false); // 執行完畢後，重設 flag 為 false
  }
}, [details]);
```

```
const cartCount = document.querySelectorAll(".cart-count");
function showCartAmount(){
  let sum = 0;
  for(let i=0;i<localStorage.length;i++){
    const { qty } = JSON.parse(localStorage.getItem(localStorage.key(i)));
    sum += qty;
  }
  cartCount.forEach(cart => cart.textContent = sum);
}
```
### React vs Vanilla
[命令式/宣告式](https://medium.com/@chih.hsi.chen/imperative-programming-vs-declarative-programming-%E8%BC%95%E9%AC%86%E8%AE%80%E6%87%82%E5%91%BD%E4%BB%A4%E5%BC%8F%E8%88%87%E5%AE%A3%E5%91%8A%E5%BC%8F%E5%87%BD%E5%BC%8F-75a1ea80b6d8)
[YUMY React Hook筆記](https://hackmd.io/@yiting444/HJFvrFc3j) | [8種常用的hook](https://youtu.be/VzNNjNmbXpY)
### useState細節
* 想觀察state的變化,console.log(state)要放在render的階段
* setState在render前還不會更新state:
```
const [state,setState] = useState(0);

function add(){
    setState(state + 1);    //re-render前state不會變,所以這行無效
    setState(state + 2);
}

function add(){
    setState(prev = prev + 1);    //但這樣可以
    setState(prev = prev + 2);
}
```
* Too many re-render錯誤(通常是setState造成的無限循環)
## useReducer
1.先檢查state跟畫面的關係對不對
2.寫reducer邏輯
3.一個一個dispatch檢查
```
dispatch({
    type:'PRESS_CHARACTER',    //命名的藝術
    payload:{
        value: e.key.toUpperCase();
    }
})
```