// 삭제 버튼 클릭시, 발생할 수 있는 이벤트
dispatch({ type: 'REMOVE_CART_ITEM', item }); // 단일 아이템 개별 삭제
dispatch({ type: 'REMOVE_SELECTED_CART_ITEM', item }); // 복수 아이템 선택 삭제
function cartReducer(state, action) {
switch (action.type) {
case 'REMOVE_CART_ITEM':
return {
...state,
allSelected: state.cart
.filter(
item =>
item.name !== action.item.name || item.size === action.item.size
)
.every(item => item.selected),
};
case 'REMOVE_SELECTED_CART_ITEM':
return {
...state,
allSelected: state.cart
.filter(
item =>
item.name !== action.item.name || item.size === action.item.size
)
.every(item => item.selected),
};
}
}
위 코드는 장바구니의 아이템을 1개씩 삭제하거나, 혹은 여러개를 선택하여 삭제할 때 실행되는 리듀서 함수 코드입니다.
액션이 디스패칭될 때, 이 코드는 1가지 상황을 제외하고 잘 작동합니다.
장바구니 리스트에서 모든 아이템을 삭제할 때를 제외하고 말이죠!
장바구니에 아이템이 존재하지 않는다면, 아이템 자체가 없으므로 전체 선택 박스는 무조건 해제가 되어야 합니다.
그러나, 이 코드는 무조건 선택된 상태를 만듭니다. 무엇이 잘못된 걸까요?
먼저, 로직에 사용된 메서드에 대해 살펴보겠습니다.
every 메서드의 동작 방식은 다음과 같습니다.
콜백함수가 해당 배열에 대해 false를 리턴하는 요소를 찾으면 그 즉시, false를 리턴합니다.
어떤 요소도 false를 리턴하지 않을 경우, true를 리턴합니다.
즉, false를 찾을 때까지 배열을 반복합니다.
그런데 빈 배열에서 호출된다면 어떻게 될까요?
어떤 요소도 false를 리턴하지 않으므로, 항상 true를 리턴합니다.
코드를 다시 살펴보겠습니다.
state.cart
.filter(
item => item.name !== action.item.name || item.size === action.item.size
)
.every(item => item.selected);
every 메서드를 실행하기 앞서, 실행되는 filter 메서드는 삭제되지 않을 요소들을 담은 배열을 리턴합니다.
만약 총 1개 중에 1개를 삭제하거나, 2개 중 2개를 모두 선택해서 삭제 하려고 한다면, 이 배열은 빈 배열이 될 것입니다.
여기서 문제가 발생하는 것이죠. 빈 배열에 대해서 every 메서드는 항상 true를 리턴한다고 되어 있으니, 전체 선택 상태가 항상 true가 됩니다.
사용한 메서드를 통해 해당 버그의 원인을 찾을 수 있었습니다.
하지만 이 버그를 어떻게 해결할 수 있을까요? 다른 메서드를 사용해야 할까요?
다른 메서드를 이용하는 방법은 있을 것 같지만, every 메서드만큼 간단하게 표현해내기는 어려울 것 같습니다.
그렇다면, every 메서드를 유지하기 위한 방법을 찾아보겠습니다.
먼저, 버그의 원인은 filter 메서드가 리턴하는 배열이 빈 배열이기 때문입니다.
이 배열이 빈 배열이 나오는 경우를 계산해보면,
filter 메서드는 삭제하려고 선택한 아이템을 제외한 아이템들만 리턴합니다.
즉, 빈 배열을 리턴한다 = 아이템이 모두 선택된 상태 를 나타냅니다.
사용자가 장바구니 아이템을 삭제하려고 할 때는, 2가지의 액션이 가능합니다.
1. 아이템별 개별 삭제 (action type: REMOVE_ITEM)
2. 복수 아이템 선택 후 삭제 (action type: REMOVE_SELECTED_ITEM)
1번의 경우는, 전체 아이템이 1개밖에 없을 때 해당 아이템마저 삭제하려고 한다면 빈 배열을 리턴할 것입니다.
2번의 경우는, 전체 아이템을 모두 선택하고 삭제하면 빈 배열을 리턴하겠죠?
위 경우를 고려하여, 코드를 변경하면 됩니다.
filter 메서드가 빈 배열이 나오지 않도록 제어하는 것은 불가능하기 때문에, filter 메서드 전에 조건문을 추가합니다.
해당 조건에는 filter 메서드를 실행하지 않고 false를 리턴하도록 강제하면, 빈 배열일 경우에는 항상 false가 리턴되어 전체 선택 버튼이 해제 상태가 될 것입니다.
function cartReducer(state, action) {
switch (action.type) {
case 'REMOVE_CART_ITEM':
return {
...state,
allSelected:
state.cart.length - 1 > 0
? state.cart
.filter(
item =>
item.name !== action.item.name ||
item.size !== action.item.size
)
.every(item => item.selected)
: false,,
};
case 'REMOVE_SELECTED_CART_ITEM':
return {
...state,
allSelected:
state.cart.filter(item => item.selected).length === state.cart.length
? false
: state.cart
.filter(item => !item.selected)
.every(item => item.selected),
};
}
}
조건부 렌더링 코드를 작성하게 되면, 위 버그를 신경 쓰지 않아도 문제가 되지는 않습니다.
하지만, 상태를 기반으로 렌더링하기 때문에 정확한 상태값을 전달할 필요가 있기 때문에 위와 같은 코드를 작성하는 것이 더 좋다고 생각합니다.
function Cart() {
return <>{cart.length > 0 ? <RenderCartList /> : <RenderNone />}</>;
}