# 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才能解決
報錯:

```
//子組件檔案
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();
}
})
```