// authSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { User } from './usersSlice';
interface AuthState {
isLogin: boolean;
user: User;
}
const initialState: AuthState = {
isLogin: false,
user: {
id: '',
pw: '',
cart: [],
},
};
const authSlice = createSlice({
name: 'auth',
initialState: initialState,
reducers: {
login: (state, action: PayloadAction<User>) => {
state.isLogin = true;
state.user = action.payload;
},
logout: (state) => {
state.isLogin = false;
state.user = {
id: '',
pw: '',
cart: [],
};
},
addCart: (state, action: PayloadAction<number>) => {
if (state.isLogin && state.user.cart) {
state.user.cart.push({ productId: action.payload }); // (productId: 인자로 받은 payload)를 push한다.
}
},
removeCart: (state, action: PayloadAction<number>) => {
if (state.isLogin && state.user.cart) {
state.user.cart = state.user.cart.filter((product) => product.productId !== action.payload);
}
},
},
});
export const { login, logout, addCart, removeCart } = authSlice.actions;
export default authSlice.reducer;
addCart
와 removeCart
를 추가해준다.
// GoodsCard.tsx
function GoodsCard({ id, img, title, artist, price }: GoodsCardProps): React.ReactElement
// other codes...
const handleCartButton = (isLogin: boolean, e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (!isLogin) {
Swal.fire({
title: '로그인 후 장바구니 이용 가능합니다.',
confirmButtonText: '확인',
confirmButtonColor: '#0E1B4E',
customClass: {
popup: 'swal-popup',
title: 'swal-title',
},
scrollbarPadding: false,
});
} else {
const isExist = user.cart?.some((list) => {
return list.productId === id;
});
if (isExist) {
Swal.fire({
title: '이미 장바구니에 담긴 상품입니다.',
confirmButtonText: '확인',
confirmButtonColor: '#0E1B4E',
customClass: {
popup: 'swal-popup',
title: 'swal-title',
},
scrollbarPadding: false,
});
} else {
dispatch(addCart(id));
Swal.fire({
title: '장바구니에 정상적으로 추가되었습니다.',
confirmButtonText: '장바구니로 이동',
confirmButtonColor: '#0E1B4E',
showCancelButton: true,
cancelButtonText: '쇼핑 계속하기',
cancelButtonColor: '#0E1B4E',
focusCancel: true,
customClass: {
popup: 'swal-popup',
title: 'swal-title',
},
scrollbarPadding: false,
}).then((result) => {
if (result.isConfirmed) {
navigate('/cart');
}
});
}
}
};
개별 상품의 장바구니 추가 버튼 클릭 시
로그인 여부 체크
이미 추가한 상품인지 체크
위 과정 모두 통과 시 dispatch(addCart(id));
// Cart.tsx
function Cart() {
const user = useAppSelector((state) => state.auth.user) as User;
const cartProductsId = user.cart;
// 방법 1. goods의 id 순서대로 - some()
// const cart = goods.filter((e) => {
// return cartProductsId?.some((cartProductId) => cartProductId.productId === e.id);
// });
// 방법 2. 장바구니에 추가한 순서대로 - find()
const cartList = (cartProductsId || [])
.map((productId) => {
return goods.find((e) => e.id === productId.productId);
})
// 타입 오류 방지를 위해 undefined 제거
.filter((product): product is (typeof goods)[number] => product !== undefined);
return (
<Inner>
<PageTitle korean>장바구니</PageTitle>
{cartList.length > 0 ? (
<>
<ProductWrapper>
{cartList.map((product) => (
<CartProduct
key={product.id}
id={product.id}
img={product.img}
title={product.title}
artist={product.artist}
price={product.price}
/>
))}
</ProductWrapper>
<OrderWrapper>
<OrderButton type='submit' variant='contained'>
주문하기
</OrderButton>
</OrderWrapper>
</>
) : (
<NoProduct>장바구니에 담긴 상품이 존재하지 않습니다.</NoProduct>
)}
</Inner>
);
}
방법은 두 가지.
Way 1
some()
을 이용하여 전체 상품(goods) 중 user의 cart 배열에 있는 id(cartProductsId)와 일치하는 상품을 true/false로 반환 후 filter()
Way 2
user의 cart 배열에 있는 id(cartProductsId)에 전체 상품(goods) 기준으로 map()
을 돌려서 id가 일치하는 것을 find()
나는 두 번째 방법을 사용.
이 때, undefined 타입 오류가 발생하여 filter()
를 통해 undefined 요소를 모두 제외할 수 있도록 함.
// CartProductCard.tsx
function CartProductCard({ id, img, title, artist, price }: CartProductProps): React.ReactElement {
const dispatch = useAppDispatch();
const [count, setCount] = useState(1);
const [sumPrice, setSumPrice] = useState(price);
const removeProduct = (id: number) => {
dispatch(removeCart(id));
};
const handlePrice = (number: number) => {
let price = number.toLocaleString();
return price;
};
const handleCount = (action: string) => {
if (action === 'minus' && count > 1) {
setCount((prev) => prev - 1);
} else if (action === 'plus') {
setCount((prev) => prev + 1);
}
};
useEffect(() => {
setSumPrice(price * count);
}, [count, price]);
return (
<Container>
<ImgWrapper>
<img src={`${img}`} alt={`${title} by ${artist}`}></img>
</ImgWrapper>
<ProductInfo>
<Title>{title}</Title>
<Artist>{artist}</Artist>
</ProductInfo>
<CounterContainer>
<IconButton size='large' onClick={() => handleCount('minus')}>
<RemoveCircleOutlineIcon />
</IconButton>
<Counter>
<strong>{count} 개</strong>
</Counter>
<IconButton size='large' onClick={() => handleCount('plus')}>
<AddCircleOutlineIcon />
</IconButton>
</CounterContainer>
<Price>
<strong>{handlePrice(sumPrice)}</strong> 원
</Price>
<IconButton size='large' onClick={() => removeProduct(id)}>
<ClearIcon />
</IconButton>
</Container>
);
}
삭제 버튼 만들어주고 onClick 시 dispatch(removeCart(id));
action
을 활용하여 수량 증가/감소를 하나의 함수에서 제어