ํ๋ก์ ํธ๋ฅผ ํ๋ฉฐ ๋ ํฐ ๋ฒ์์ ์ํ ๊ด๋ฆฌ๋ฅผ ํ๊ฒ ๋์๋ค.
๊ทธ๋ฆฌ๊ณ ํ์ ์ฌ์ฉํ์ง ์์๋ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ธฐ๋ ํ๋ค. ์๋ฅผ ๋ค์ด inNaN ์ ๊ฒฝ์ฐ ์ฌ์ค ์ ์์๊น? ํ๋ ๋ถ๋ถ์ด์๋ค. ํ์ง๋ง ์~ ์ด๋ด ๋ ์ฌ์ฉํ๊ตฌ๋๋ฅผ ๊นจ์ฐ์น๋ฉฐ ๋ ์ฑ์ฅํ ํ๋ก์ ํธ์๋ค.
ํ๋ก์ ํธ ๊ธฐ๊ฐ์๋ ๋๋ฌด ๋ฟ๋ฏํด์ ๋ธ๋ก๊ทธ๋ฅผ ๋ฐ๋ก ์์ฑํ๊ณ ์ถ์์ง๋ง ๋ค์ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด ๋ฉ๋ชจ ํ๋์ด๊ฐ์ผ ํ๊ธฐ์ ์ด์ ์์ฑํด ๋ณด๋ ค ํ๋ค~
// onChange์ ๋ค์ด๊ฐ๋ ๋ถ๋ถ
const changeQuantityByInput = e => {
const value = e.target.value;
if (isNaN(value)) return;
if (value === '0') {
setQuantity(1);
alert('์ต์ ์ฃผ๋ฌธ ์๋์ 1๊ฐ ์
๋๋ค.');
return;
}
if (value > 100) {
alert('์ต๋ ์ฃผ๋ฌธ ์๋์ 100๊ฐ ์
๋๋ค.');
return;
}
setQuantity(value);
};
// onBlur์ ๋ค์ด๊ฐ๋ ๋ถ๋ถ
const checkMinmumQuantity = e => {
if (!e.target.value) {
setQuantity(1);
alert('์ต์ ์ฃผ๋ฌธ ์๋์ 1๊ฐ ์
๋๋ค.');
}
};
<input
className="quantity"
type="text"
value={quantity}
onChange={changeQuantityByInput}
onBlur={checkMinmumQuantity}
/>
if (!cartBtn.current.contains(e.target))
function Product({ product, putInfoIntoModal }) {
const { name, image, price, introduction, id } = product;
const cartBtn = useRef();
const navigate = useNavigate();
const gotoProductDetail = e => {
if (!cartBtn.current.contains(e.target)) {
navigate(`product/${id}`);
}
};
return (
<article className="product" onClick={gotoProductDetail}>
<div className="imgContainer">
<img src={image} alt={name} />
<button
className="cartBtn"
ref={cartBtn}
onClick={() => putInfoIntoModal(product)}
>
<AiOutlineShoppingCart />
</button>
</div>
<h3 className="name">{name}</h3>
<span className="price">{Number(price).toLocaleString()}์</span>
<span className="information">{introduction}</span>
</article>
);
}
searchParams.get์ผ๋ก ์ง๊ธ query ๋ฅผ ์ฝ์ด ๊ฐ์ ์ ์ฅํ๋ค.
1-1. menu, category, sort์ query ๊ฐ์ ๋ฐ๊ณ ๊ฐ์ด ์์ ๊ฒฝ์ฐ ๊ธฐ๋ณธ๊ฐ์ ์ค์ ํด์ ๋ณ์์ ์ ์ฅํ๋ค.
์ฒซ ๋ฉ์ธ ํ์ด์ง์ด๊ฑฐ๋ menu๋ง ์๋ ๊ฒฝ์ฐ๋ฅผ category, sort๋ฅผ ์ง์ ํด์ ์ฟผ๋ฆฌ์ ๋ณด์ฌ์ฃผ๋๋ก ํ๋ค.
2-1. endpoint '/' ์ธ ๊ฒฝ์ฐ(์ฟผ๋ฆฌ์ menu ๋ถ๋ถ์ด ์์ง ํ ๋น๋์ง ์์๋ค๋ฉด)
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.set('category', '');
newSearchParams.set('sort', '-created_at');
setSearchParams(newSearchParams);
setCurrentCategory(0);
setCurrentSort(0);
const changeCategoty = (id, category) => {
const newSearchParams = new URLSearchParams(searchParams);
if (category === '์ ์ฒด๋ณด๊ธฐ') {
newSearchParams.set('category', '');
} else {
newSearchParams.set('category', category);
}
setSearchParams(newSearchParams);
setCurrentCategory(id);
};
const changeSort = (id, sortName) => {
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.set('sort', sortName);
setSearchParams(newSearchParams);
setCurrentSort(id);
};
function ProductList() {
const [currentCategory, setCurrentCategory] = useState(0);
const [currentSort, setCurrentSort] = useState(0);
const [productMenu, setProductMenu] = useState(null);
const [products, setProducts] = useState([]);
const [cartInfo, setCartInfo] = useState({});
const [isCartModalOpen, setIsCartModalOpen] = useState(false);
const [searchParams, setSearchParams] = useSearchParams();
const [loaded, setLoaded] = useState(false);
const menu = searchParams.get('menu') || '์ฑ์';
const category = searchParams.get('category') || '';
const sort = searchParams.get('sort') || '-created_at';
useEffect(() => {
setProductMenu(PRODUCT_MENU[menu]);
if (!searchParams.get('menu')) {
return;
}
if (category === '' && sort === '-created_at') {
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.set('category', '');
newSearchParams.set('sort', '-created_at');
setSearchParams(newSearchParams);
setCurrentCategory(0);
setCurrentSort(0);
}
}, [category, menu, searchParams, setSearchParams, sort]);
useEffect(() => {
fetch(
`${API.product}?menu=${menu}${
!category.length ? '' : `&category=${category}`
}&sort=${sort}`
)
.then(res => res.json())
.then(res => {
if (!!res.result) {
setProducts(res.result);
}
switch (res.message) {
case 'AttributeError':
case 'KeyError':
case 'TypeError':
case 'DoesNotExits':
alert('์๋ฌ์
๋๋ค.');
break;
default:
break;
}
})
.catch(e => {
// eslint-disable-next-line no-console
console.error(e);
})
.finally(setLoaded(true));
}, [category, menu, searchParams, sort]);
const putInfoIntoModal = product => {
setCartInfo(product);
setIsCartModalOpen(true);
};
const closeModal = () => {
setCartInfo({});
setIsCartModalOpen(false);
};
const changeCategoty = (id, category) => {
const newSearchParams = new URLSearchParams(searchParams);
if (category === '์ ์ฒด๋ณด๊ธฐ') {
newSearchParams.set('category', '');
} else {
newSearchParams.set('category', category);
}
setSearchParams(newSearchParams);
setCurrentCategory(id);
};
const changeSort = (id, sortName) => {
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.set('sort', sortName);
setSearchParams(newSearchParams);
setCurrentSort(id);
};
return (
<section className="productList">
<div className="productListContent">
{productMenu && (
<ProductListHeader
productMenu={productMenu}
currentCategory={currentCategory}
changeCategoty={changeCategoty}
/>
)}
{loaded && !products.length ? (
<h2 className="loading">์ํ ์์</h2>
) : (
<ProducListContent
products={products}
changeSort={changeSort}
currentSort={currentSort}
putInfoIntoModal={putInfoIntoModal}
/>
)}
</div>
{isCartModalOpen && (
<Modal closeModal={closeModal}>
<CartModal closeModal={closeModal} product={cartInfo} />
</Modal>
)}
</section>
);
}
์ข ์ข ๋์ผํ ๋ฐ์ดํฐ์ ๋ํ ๋ณ๊ฒฝ์ฌํญ์ ์ฌ๋ฌ ์ปดํฌ๋ํธ์ ๋ฐ์ํด์ผ ํ ํ์๊ฐ ์์ต๋๋ค. ์ด๋ด ๋๋ ๊ฐ์ฅ ๊ฐ๊น์ด ๊ณตํต ์กฐ์์ผ๋ก state๋ฅผ ๋์ด์ฌ๋ฆฌ๋ ๊ฒ์ด ์ข์ต๋๋ค. - React ๊ณต์๋ฌธ์ -
import React, { useEffect, useState } from 'react';
import './Cart.scss';
import Items from './Items/Items';
import SelectBtns from './SelectBtns/SelectBtns';
import CartSummary from './CartSummary/CartSummary';
function Cart() {
const [items, setItems] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [coldItems, setColdItems] = useState([]);
const [boxItems, setBoxItems] = useState([]);
const [checkedItems, setCheckedItems] = useState(0);
const [isAllchecked, setIsAllchecked] = useState(true);
useEffect(() => {
fetch(`/data/cartItemsData.json`)
});
}, []);
useEffect(() => {
if (!isLoaded) return;
if (!items || !items.length) return;
let coldArray = [];
let boxArray = [];
items.forEach(item => {
if (item.itemPackage === 'cold') {
coldArray.push(item);
} else {
boxArray.push(item);
}
});
setColdItems(coldArray);
setBoxItems(boxArray);
setCheckedItems(Items.length);
}, [items, isLoaded]);
useEffect(() => {
// ์ฒซ ๋ก๋ฉ์์๋ ์์ดํ
์ ์ฒด๊ฐ ์ฒดํฌ๋๊ธฐ ๋๋ฌธ์ ์ด๋ถ๋ถ ์ฒ๋ฆฌํ์ง ์์๋๋จ
if (!isLoaded) return;
// undefined(์ฒ์, ์ฒดํฌ ์๋์ง ์์์ ๋) , ์ฒดํฌ๋ ๊ฒ -> false / ์ฒดํฌ์๋ ๊ฒ -> true ๋ก ํด์
// ์ด๋ถ๋ฒ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํด
const notCheckedColdItems = coldItems.filter(
item => item.notChecked
).length;
const notCheckedBoxItems = boxItems.filter(item => item.notChecked).length;
const itemsLength = coldItems.length + boxItems.length;
const checkedItemsLength =
itemsLength - notCheckedColdItems - notCheckedBoxItems;
setCheckedItems(checkedItemsLength);
setIsAllchecked(itemsLength === checkedItemsLength);
}, [coldItems, boxItems, isLoaded]);
const changeItemQuantity = (id, changedQuantity, itemPackage) => {
if (itemPackage === 'cold') {
setColdItems(
coldItems.map(item =>
item.id !== id ? item : { ...item, quantity: changedQuantity }
)
);
} else {
setBoxItems(
boxItems.map(item =>
item.id !== id ? item : { ...item, quantity: changedQuantity }
)
);
}
};
const deleteItem = (id, itemPackage) => {
// TODO fetch
if (itemPackage === 'cold') {
setColdItems(coldItems.filter(item => item.id !== id));
} else {
setBoxItems(boxItems.filter(item => item.id !== id));
}
};
const deleteAllCheckedItem = () => {
setColdItems(coldItems.filter(item => item.notChecked));
setBoxItems(boxItems.filter(item => item.notChecked));
};
const changeAllItemsCheck = isAllChecked => {
setColdItems(
coldItems.map(item => {
return { ...item, notChecked: isAllChecked };
})
);
setBoxItems(
boxItems.map(item => {
return { ...item, notChecked: isAllChecked };
})
);
};
const checkAllItems = () => {
setIsAllchecked(!isAllchecked);
changeAllItemsCheck(isAllchecked);
};
const changeItemCheck = (id, itemPackage) => {
if (itemPackage === 'cold') {
setColdItems(
coldItems.map(item =>
item.id !== id ? item : { ...item, notChecked: !item.notChecked }
)
);
} else {
setBoxItems(
boxItems.map(item =>
item.id !== id ? item : { ...item, notChecked: !item.notChecked }
)
);
}
};
const orderItems = () => {
if (checkedItems < 1) {
alert('์ฃผ๋ฌธํ์ค ์ํ์ ์ ํํด์ฃผ์ธ์');
return;
}
return (
<section className="cart">
<div className="cartWrapper">
<h2 className="cartTitle">์ฅ๋ฐ๊ตฌ๋</h2>
<main className="main">
<div className="cartContent">
<SelectBtns
checkedItems={checkedItems}
itemsLength={coldItems.length + boxItems.length}
checkAllItems={checkAllItems}
isAllchecked={isAllchecked}
deleteAllCheckedItem={deleteAllCheckedItem}
/>
<div className="itemsWrapper">
<Items
title="๋์ฅ ์ํ"
items={coldItems}
changeItemQuantity={changeItemQuantity}
deleteItem={deleteItem}
changeItemCheck={changeItemCheck}
/>
<Items
title="์์จ ์ ํ"
items={boxItems}
changeItemQuantity={changeItemQuantity}
deleteItem={deleteItem}
changeItemCheck={changeItemCheck}
/>
</div>
<SelectBtns
checkedItems={checkedItems}
itemsLength={coldItems.length + boxItems.length}
checkAllItems={checkAllItems}
isAllchecked={isAllchecked}
deleteAllCheckedItem={deleteAllCheckedItem}
/>
</div>
<CartSummary
coldItems={coldItems}
boxItems={boxItems}
orderItems={orderItems}
/>
</main>
</div>
</section>
);
import React, { useEffect, useState } from 'react';
import Items from './Items/Items';
import SelectBtns from './SelectBtns/SelectBtns';
import CartSummary from './CartSummary/CartSummary';
import './Cart.scss';
function Cart() {
const [items, setItems] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
fetch(`/data/cartItemsData.json`)
});
}, []);
let coldItems = [];
let boxItems = [];
items.forEach(item => {
if (item.itemPackage === 'cold') {
coldItems.push(item);
} else {
boxItems.push(item);
}
});
const checkedItemsLength =
items.length - items.filter(item => item.notChecked).length;
const changeItemQuantity = (id, changedQuantity) => {
setItems(
items.map(item =>
item.id !== id ? item : { ...item, quantity: changedQuantity }
)
);
};
const deleteItem = id => {
setItems(items.filter(item => item.id !== id));
};
const deleteAllCheckedItem = () => {
setItems(items.filter(item => item.notChecked));
};
const changeAllItemsCheck = changedCheck => {
setItems(
items.map(item => {
return { ...item, notChecked: changedCheck };
})
);
};
const checkAllItems = () => {
const isAllChecked = items.length === checkedItemsLength;
changeAllItemsCheck(isAllChecked);
};
const changeItemCheck = id => {
setItems(
items.map(item =>
item.id !== id ? item : { ...item, notChecked: !item.notChecked }
)
);
};
const orderItems = () => {
if (checkedItemsLength < 1) {
alert('์ฃผ๋ฌธํ์ค ์ํ์ ์ ํํด์ฃผ์ธ์');
return;
}
return (
<section className="cart">
<div className="cartWrapper">
{!isLoaded ? (
<h2 className="loading">๋ก๋ฉ์ค...</h2>
) : (
<>
<h2 className="cartTitle">์ฅ๋ฐ๊ตฌ๋</h2>
<main className="main">
<div className="cartContent">
<SelectBtns
checkedItemsLength={checkedItemsLength}
itemsLength={items.length}
checkAllItems={checkAllItems}
deleteAllCheckedItem={deleteAllCheckedItem}
/>
<div className="itemsWrapper">
<Items
title="๋์ฅ ์ํ"
items={coldItems}
changeItemQuantity={changeItemQuantity}
deleteItem={deleteItem}
changeItemCheck={changeItemCheck}
/>
<Items
title="์์จ ์ ํ"
items={boxItems}
changeItemQuantity={changeItemQuantity}
deleteItem={deleteItem}
changeItemCheck={changeItemCheck}
/>
</div>
<SelectBtns
checkedItemsLength={checkedItemsLength}
itemsLength={items.length}
checkAllItems={checkAllItems}
deleteAllCheckedItem={deleteAllCheckedItem}
/>
</div>
<CartSummary
coldItems={coldItems}
boxItems={boxItems}
orderItems={orderItems}
/>
</main>
</>
)}
</div>
</section>
);