(React) 버튼 2번 눌리는 현상
현상 :
React 로 장바구니에 주문상품을 담는 이벤트를 구현했다.
그런데 이때 같은 상품을 여러개 담기위해 장바구니에 담는 버튼을 연속으로 눌렀는데
한번 누를 때마다 기존수량에 +2 씩 눌리는 현상이 나타났다.
**문제의 코드
//장바구니 내역 데이터는 아래와 같은 식이다.
const prevOrder = [ { id: 1, name: "커피", quantity: 1, options=[] }, { id: 2, name: "티", quantity: 2 ,options=[] } ];
const addToOrder = useCallback((item: MenuItem, selectedOptionIds: string[]) => {
const selectedOptionObjects = item.options?.filter((option) => selectedOptionIds.includes(option.id)) || []
setOrder((prevOrder) => {
const existingItemIndex = prevOrder.findIndex(
(orderItem) =>
orderItem.id === item.id &&
JSON.stringify(orderItem.selectedOptions) === JSON.stringify(selectedOptionObjects),
)
if (existingItemIndex !== -1) {
// 기존 항목이 있으면 수량을 1 증가
return prevOrder.map((orderItem, index) =>
index === existingItemIndex ? { ...orderItem, quantity: orderItem.quantity + 1 } : orderItem,
)
} else {
// 새 항목 추가
return [...prevOrder, { ...item, quantity: 1, selectedOptions: selectedOptionObjects, note: "" }]
}
})
setSelectedOptions((prev) => ({ ...prev, [item.id]: [] }))
}, [])
**작동에러 원인 분석
1. 주요 문제: setOrder()의 비동기 업데이트로 인한 상태 불일치.
위의 코드 중 기존 항목의 수량을 증가시킬 때 다음과 같은 로직을 사용했다.
```javascript
if (existingItemIndex !== -1) {
return prevOrder.map((orderItem, index) =>
index === existingItemIndex
? { ...orderItem, quantity: orderItem.quantity + 1 }
: orderItem
)
}
```
이 로직은 겉보기에는 정상적으로 보이지만, React의 상태 업데이트 방식과 관련하여 문제가 있었다.
2. 문제의 세부 원인:
a. 상태 업데이트의 비동기성: React에서 상태 업데이트는 비동기적으로 일어난다. 즉, `setOrder` 함수를 호출한 직후에 상태가 즉시 업데이트되지 않는다.
b. 클로저(Closure)와 이전 상태: `addToOrder` 함수가 호출될 때마다, 이 함수는 호출 시점의 `prevOrder` 상태를 참조한다. 빠르게 연속해서 함수를 호출하면, 각 호출이 동일한 이전 상태를 기반으로 실행될 수 있다.
3. 문제의 결과:
위의 요인들로 인해, 사용자가 빠르게 여러 번 "주문 추가" 버튼을 클릭하면, 각 클릭이 이전 상태를 기반으로 수량을 1 증가시키려 했지만, 실제로는 마지막 업데이트만 적용되어 수량이 2씩 증가하는 것처럼 보였다.
4. 해결 방법:
수정된 코드에서는
```javascript
if (existingItemIndex !== -1) {
return prevOrder.map((orderItem, index) =>
index === existingItemIndex
? { ...orderItem, quantity: orderItem.quantity + 1 }
: orderItem
)
} else {
return [...prevOrder, { ...item, quantity: 1, selectedOptions: selectedOptionObjects, note: "" }]
}
```
이 접근 방식은 다음과 같은 이유로 문제를 해결할 수 있다.:
- 매번 새로운 배열을 생성하여 반환함으로써, React가 상태 변화를 확실히 감지하고 컴포넌트를 다시 렌더링하도록 했다.
- 각 업데이트가 이전 상태를 정확히 반영하도록 보장한다.
- 연속된 클릭에 대해 각각의 상태 업데이트가 독립적으로 처리되도록 했다.