๐Ÿฅฆ๋ธŒ๋กœ์ปฌ๋ฆฌ๐Ÿฅฆ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ ํ”„๋กœ์ ํŠธ ์ค‘ ๋งŒ๋“  ๋ฟŒ๋“ฏํ•œ ์„ฑ์žฅ ์ฝ”๋“œ ๊ธฐ๋ก

Maria Kimยท2021๋…„ 12์›” 12์ผ
0
post-thumbnail
post-custom-banner

ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉฐ ๋” ํฐ ๋ฒ”์œ„์˜ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ‰์†Œ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ–ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด inNaN ์˜ ๊ฒฝ์šฐ ์‚ฌ์‹ค ์™œ ์žˆ์„๊นŒ? ํ•˜๋Š” ๋ถ€๋ถ„์ด์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์•„~ ์ด๋Ÿด ๋•Œ ์‚ฌ์šฉํ•˜๊ตฌ๋‚˜๋ฅผ ๊นจ์šฐ์น˜๋ฉฐ ๋” ์„ฑ์žฅํ•œ ํ”„๋กœ์ ํŠธ์˜€๋‹ค.

ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„์—๋Š” ๋„ˆ๋ฌด ๋ฟŒ๋“ฏํ•ด์„œ ๋ธ”๋กœ๊ทธ๋ฅผ ๋ฐ”๋กœ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ์—ˆ์ง€๋งŒ ๋‹ค์Œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ๋ฉ”๋ชจ ํ›„๋„˜์–ด๊ฐ€์•ผ ํ–ˆ๊ธฐ์— ์ด์ œ ์ž‘์„ฑํ•ด ๋ณด๋ ค ํ•œ๋‹ค~

์ˆ˜๋Ÿ‰์€ ๋ฒ„ํŠผ๋งŒ์ด ์•„๋‹ˆ๋ผ text๋กœ๋„ ์ ํ˜€์•ผํ•œ๋‹ค๋‹ˆ?!

์™„์„ฑ ์‹œ์—ฐ

๋ฐฐ๊ฒฝ

  • ์ˆ˜๋Ÿ‰ ๋ณ€๊ฒฝ ๋ถ€๋ถ„์— ์›๋ž˜ - ์™€ + ์„ ํด๋ฆญํ•˜๋ฉด 1 ๋‹จ์œ„๋กœ ๋ณ€๊ฒฝ๋˜๋„๋ก ํ–ˆ์œผ๋ฉฐ ์ˆ˜๋Ÿ‰์„ ๋‚˜ํƒ€๋‚ด๋Š” 1์˜ ๊ฒฝ์šฐ span์œผ๋กœ ์ž‘์„ฑํ–ˆ์—ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ ์ƒ๊ฐํ•ด ๋ณด๋ฉด ํฐ ๋‹จ์œ„๋ฅผ ์‹œํ‚ค๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ, ์˜ˆ๋กœ 50๊ฐœ๋ฅผ ๋„ฃ๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ ์†Œ๋น„์ž์—๊ฒŒ +๋ฅผ 50๋ฒˆ ํด๋ฆญํ•˜๊ฒŒ๋” ํ•˜๋Š” ๊ฒƒ์€ ์ •๋ง ux ์ƒ ์ข‹์ง€ ์•Š๋‹ค. ์†”์งํžˆ 50๊ฐœ ์‚ด๋ ค๊ณ  ๋งˆ์Œ๋จน์—ˆ๋”๋ผ๋„ ์งœ์ฆ ๋‚˜์„œ 15๊ฐœ์ฏค ํด๋ฆญํ•˜๋‹ค๊ฐ€ ์‚ฌ์ง€ ์•Š๊ฒ ๋Š”๊ฐ€?
  • ๊ทธ๋ž˜์„œ ์ˆ˜๋Ÿ‰์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ถ€๋ถ„์„ span ํƒœ๊ทธ์—์„œ input ํƒœ๊ทธ๋กœ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

๋ฌธ์ œ

  • input์— ์ˆซ์ž๋งŒ ๋„ฃ๊ณ  ์‹ถ์œผ๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?
  • text ๋ถ€๋ถ„์„ ๋‹ค ์—†์• ๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?

์‹œํ–‰์ฐฉ์˜ค

1. input type = number

์‹œ๋„ํ•œ ์ด์œ 

  • ์ˆซ์ž๋งŒ ๋“ค์–ด๊ฐ€๋„๋ก ๋งŒ๋“ค์–ด์•ผ๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋‹ˆ input type = number์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

๋ฌธ์ œ

  • ์ด๋ ‡๊ฒŒ ์ˆ˜๋Ÿ‰ ๋ถ€๋ถ„์— ์•„๋ฌด๊ฒƒ๋„ ์—†๋„๋ก ๋งŒ๋“ค๊ณ  ์‹ถ์€๋ฐ type = number๋กœ ํ•˜๋ฉด ๊ผญ 1๊ฐœ์˜ ์ˆซ์ž๋Š” ๋‚จ์•„์žˆ์–ด์•ผ ํ–ˆ๋‹ค.

2. input type = text & ์ •๊ทœ์‹

์‹œ๋„ํ•œ ์ด์œ 

  • ๊ทธ๋ ‡๋‹ค๋ฉด ๋‚จ์€ ๋ฐฉ๋ฒ•์€ type = text

๋‚˜์•„์ง„ ๋ถ€๋ถ„

  • ๋นˆ์นธ์ด ๋˜๋„๋ก ๋‹ค ์ง€์›Œ์ง„๋‹ค

๋ฌธ์ œ

  • ํ•˜์ง€๋งŒ ๋ฌธ์ œ๋Š” ์–ด๋–ป๊ฒŒ ์ˆซ์ž๋งŒ ์ฒ˜๋ฆฌ๋˜๋„๋ก ํ• ๊นŒ?
    • ์ •๊ทœ์‹์„ ์“ธ๊นŒ?
      -> ์ •๊ทœ์‹์„ ์‚ฌ์šฉํ•ด์„œ ์ˆซ์ž๋งŒ ์ ํž ๋•Œ ์ˆ˜๋Ÿ‰์ด ์ ํžˆ๋„๋ก ํ–ˆ๋”๋‹ˆ
      -> Backspace๊ฐ€ ๋จนํžˆ์ง€ ์•Š๋Š” ๊ฒƒ์ด ์•„๋‹Œ๊ฐ€...

3. input type = text & isNaN

๋‚˜์•„์ง„ ๋ถ€๋ถ„

  • ์ˆซ์ž๋งŒ ๊ฑธ๋Ÿฌ์ง€๊ณ , Backspace๋กœ ์ง€์›Œ์ง€๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅ

๋ฌธ์ œ

  • ๋นˆ์นธ์œผ๋กœ ํ•ด๋‹น ๋ถ€๋ถ„์„ ๋‚˜์˜ฌ ๊ฒฝ์šฐ ๋นˆ์นธ์ด ๋˜์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.
  • ํŠนํžˆ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์—์„œ๋Š” 0 ๋˜๋Š” ๋นˆ์นธ์„ ์˜ˆ์™ธ ์ฒ˜๋ฆฌํ•ด ์ค˜์•ผ ํ•œ๋‹ค.

ํ•ด๊ฒฐ! input type = text & isNaN & onBlur

๋‚˜์•„์ง„ ๋ถ€๋ถ„

  • onBlur ์ด๋ฒคํŠธ๋ฅผ ํ• ๋‹นํ•ด input์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋‚˜์˜ฌ ๋•Œ ๋นˆ์นธ์ด๋ฉด ์ˆ˜๋Ÿ‰์„ 1๋กœ ์กฐ์ •ํ•ด ์คฌ๋‹ค.
// 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}
   />

ํด๋ฆญํ•˜๋ฉด ์ƒ์„ธํŽ˜์ด์ง€๋กœ ๋„˜์–ด๊ฐ€๋Š” ์ƒํ’ˆ ๋ถ€๋ถ„ ์œ„์— ํด๋ฆญํ•˜๋ฉด ๋ชจ๋‹ฌ์ด ๋– ์•ผ ํ•˜๋Š” ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋ฒ„ํŠผ์ด ์žˆ๋‹ค๋ฉด?!

์™„์„ฑ ์‹œ์—ฐ

๋ฌธ์ œ

  1. ์ƒํ’ˆ ๋ถ€๋ถ„์„ ํด๋ฆญํ•˜๋ฉด ํ•ด๋‹น ์ƒํ’ˆ์˜ ์ƒ์„ธํŽ˜์ด์ง€๋กœ ์ด๋™
  2. ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์ฃผ๋ฌธ Modal์ด ๋– ์•ผ ํ•œ๋‹ค.
  • 1๊ณผ 2๊ฐ€ ๋ชจ๋‘ ํด๋ฆญ ์ด๋ฒคํŠธ๋กœ ์ผ์–ด๋‚˜๋‹ค ๋ณด๋‹ˆ
  • ์ƒํ’ˆ ๋ถ€๋ถ„ ์•ˆ์— ์žˆ๋Š” ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด
    -> ์›ํ•˜๋Š” ๊ฒƒ - ์ฃผ๋ฌธ Modal ๋œจ๊ธฐ
    -> But, ํด๋ฆญ ์ด๋ฒคํŠธ๊ฐ€ ๋ฒ„๋ธ”๋ง ๋˜์–ด ์ƒํ’ˆ์— ์žˆ๋Š” onClick์ด๋ฒคํŠธ๊ฐ€ ์‹คํ–‰๋˜์–ด ์ƒ์„ธํŽ˜์ด์ง€๋„ ์ด๋™

์›๋ž˜ ์ฝ”๋“œ

  • ์ƒํ’ˆ ๋ถ€๋ถ„ ์ „์ฒด๊ฐ€ Link ๋กœ ๊ฐ์‹ธ์ ธ ์žˆ์—ˆ๋‹ค

๋ณ€ํ™”๋œ ์ฝ”๋“œ

  • ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋ฒ„ํŠผ์— ref ๋ฅผ ์„ค์ •
  • ์ƒํ’ˆ ์ „์ฒด์— onClick ์ด๋ฒคํŠธ๋ฅผ ์ฃผ๊ณ  ๋งŒ์•ฝ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋ฒ„ํŠผ ๋ถ€๋ถ„์ด ํด๋ฆญ๋œ ๊ฒƒ์ด ์•„๋‹Œ ํด๋ฆญ๋“ค์„ ๊ฑธ๋Ÿฌ๋ƒ„ if (!cartBtn.current.contains(e.target))
  • navigate๋กœ ์ƒ์„ธํŽ˜์ด์ง€๋กœ ์ด๋™
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>
  );
}

๋ฉ”์ธ์ด ์นดํ…Œ๊ณ ๋ฆฌ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ”๋กœ ๋ณด์—ฌ์ค˜์„œ parmas๋กœ ๊ฐ ์นดํŽ˜๊ณ ๋ฆฌ๋ฅผ ์ด๋™ํ•˜๋Š” ๋ฐฉ์‹์„ ํ•  ์ˆ˜ ์—†๋‹ค๋ฉด?!

์™„์„ฑ ์‹œ์—ฐ

๋ฌธ์ œ

  • ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฉ”์ธ ํ™”๋ฉด์ด ์ƒํ’ˆ ๋ฆฌ์ŠคํŠธ ํŽ˜์ด์ง€๋ผ์„œ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ๋Š” ๋ถ€๋ถ„์„ params๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์–ด ๋ผ์šฐํ„ฐ์˜ ๋‚ด์žฅ hook์ธ useParams ๋ฅผ ์ด์šฉํ•ด์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†์—ˆ๋‹ค.
  • ๊ทธ๋ž˜์„œ query ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ๋‹ค.
  • ๋Œ€๋ถ„๋ฅ˜(menu), ์ค‘๋ถ„๋ฅ˜(category), ์ •๋ ฌ(sort)์„ ๊ด€๋ฆฌํ•ด ์„œ๋ฒ„์—๊ฒŒ ๋ณด๋‚ด๊ณ  ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์•ผ ํ•œ๋‹ค.
  • ์ œ์ผ ํฐ ๋ฌธ์ œ query๋ฅผ ์ฒ˜์Œ ์จ๋ณธ๋‹ค ใ…Žใ…Žใ…Žใ…Ž

๋ฐฉ๋ฒ•

  • ์•Œ์•„์•ผํ•˜๋Š” ๋ถ€๋ถ„: ๋Œ€๋ถ„๋ฅ˜(menu), ์ค‘๋ถ„๋ฅ˜(category), ์ •๋ ฌ(sort)
  • ์‚ฌ์šฉ react-router-dom Hook : useSearchParams()
  • new URLSearchParams();
  1. searchParams.get์œผ๋กœ ์ง€๊ธˆ query ๋ฅผ ์ฝ์–ด ๊ฐ’์„ ์ €์žฅํ•œ๋‹ค.
    1-1. menu, category, sort์˜ query ๊ฐ’์„ ๋ฐ›๊ณ  ๊ฐ’์ด ์—†์„ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•ด์„œ ๋ณ€์ˆ˜์— ์ €์žฅํ•œ๋‹ค.

  2. ์ฒซ ๋ฉ”์ธ ํŽ˜์ด์ง€์ด๊ฑฐ๋‚˜ menu๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ category, sort๋ฅผ ์ง€์ •ํ•ด์„œ ์ฟผ๋ฆฌ์— ๋ณด์—ฌ์ฃผ๋„๋ก ํ•œ๋‹ค.
    2-1. endpoint '/' ์ธ ๊ฒฝ์šฐ(์ฟผ๋ฆฌ์— menu ๋ถ€๋ถ„์ด ์•„์ง ํ• ๋‹น๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด)

  • ๋ฉ”์ธ์ธ ์ฑ„์†Œ ๋ถ€๋ถ„์„ ๋ฐ”๋กœ ๋ณด์—ฌ์ฃผ๊ฒŒ ํ•œ๋‹ค.
    2-2. '/'๊ฐ€ ์•„๋‹ˆ์ง€๋งŒ menu๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ
  • ํ˜„์žฌ url์—์„œ category ์™€ sort๋ฅผ ์ง€์ •ํ•ด ์ค€๋‹ค.
const newSearchParams = new URLSearchParams(searchParams);
newSearchParams.set('category', '');
newSearchParams.set('sort', '-created_at');
setSearchParams(newSearchParams);
setCurrentCategory(0);
setCurrentSort(0);
  1. category ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ
    3-1. ์ „์ฒด๋ณด๊ธฐ๋ฉด ๋นˆ์นธ
    3-2. ์ƒ์„ธ ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์„ ํƒ๋˜๋ฉด ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ newSearchParams.set์œผ๋กœ ์ถ”๊ฐ€ํ•ด์„œ setSearchParams์œผ๋กœ parmas๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค.
  const changeCategoty = (id, category) => {
    const newSearchParams = new URLSearchParams(searchParams);
    if (category === '์ „์ฒด๋ณด๊ธฐ') {
      newSearchParams.set('category', '');
    } else {
      newSearchParams.set('category', category);
    }
    setSearchParams(newSearchParams);
    setCurrentCategory(id);
  };
  1. sort ๋ฅผ ๋ณ€๊ฒฝํ•  ๋•Œ
  • sort ๋ถ€๋ถ„์„ ์นดํ…Œ๊ณ ๋ฆฌ์— ๋„ฃ๋Š”๋‹ค.
  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>
  );
}

์ง„๋ฆฌ์˜ ์›์ฒœ(source of truth)

๋ฆฌํŒฉํ† ๋งํ•˜๊ฒŒ ๋œ ๋ฐฐ๊ฒฝ

  • ์žฅ๋ฐ”๊ตฌ๋‹ˆ ํŽ˜์ด์ง€์—์„œ ์ƒํ’ˆ๋“ค(items)์˜ state๋ฅผ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ• ์ง€์— ๋Œ€ํ•ด ๊ณ ๋ฏผํ–ˆ๋‹ค
  • items ๋Š” ๋ƒ‰์žฅ, ์ƒ์˜จ 2 ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋‰˜์–ด ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚ฌ๋‹ค.
  • ์ƒํ’ˆ์€ ์ฒดํฌ, ์ˆ˜๋Ÿ‰ ์‚ญ์ œ๋กœ ๋ณ€๊ฒฝ์ด ๋˜๊ณ  ์žˆ์—ˆ๋‹ค.

์ž˜๋ชป ์ƒ๊ฐํ•œ ๋ถ€๋ถ„

  • ๋ƒ‰์žฅ ์ƒํ’ˆ ๋ถ€๋ถ„๋“ค์ด ๋ณ€๊ฒฝ๋˜๋ฉด ์ƒ์˜จ ๋ถ€๋ถ„์€ ๋ณ€๊ฒฝ๋  ํ•„์š”๊ฐ€ ์—†์œผ๋‹ˆ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•ด์•ผ๊ฒ ๋‹ค.

๋ณ€๊ฒฝํ•œ ์ด์œ 

  • ์–ด์ฐจํ”ผ React ๋Š” ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ ์ˆ˜์ •ํ•˜๊ธฐ์— ๋‚˜๋ˆŒ ํ•„์š”๊ฐ€ ์—†๊ณ 
  • ๋ฆฌ์•กํŠธ์˜ ์ฝ”์–ด ๊ฐœ๋…์ธ source of truth๋ฅผ ๊ณ ๋ คํ•˜๋ฉด

    ์ข…์ข… ๋™์ผํ•œ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์— ๋ฐ˜์˜ํ•ด์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿด ๋•Œ๋Š” ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ณตํ†ต ์กฐ์ƒ์œผ๋กœ state๋ฅผ ๋Œ์–ด์˜ฌ๋ฆฌ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. - React ๊ณต์‹๋ฌธ์„œ -

  • 1๊ฐœ๋กœ items๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ์ด๋ฅผ ๋ Œ๋”๋ง ํ•  ๋•Œ๋งˆ๋‹ค ์ƒ์˜จ๊ณผ ๋ƒ‰์žฅ์œผ๋กœ ๋ถ„๋ฆฌํ•ด ๋ณด์—ฌ์ฃผ๋Š” ๊ฒŒ ๋งž๋‹ค
  • ์ด๋ ‡๊ฒŒ ํ•˜๋‹ˆ ์ƒ์˜จ๊ณผ ๋ƒ‰์žฅ์œผ๋กœ ๋‚˜๋ˆ„์–ด ๋กœ์ง๋“ค์„ ๋ฐ˜๋ณตํ•ด์•ผ ํ–ˆ๋˜ ๋ถ€๋ถ„์ด ๋Œ€ํญ ์ค„์–ด๋“ค์–ด ์ฝ”๋“œ๊ฐ€ ํ™• ์ค„์—ˆ๋‹ค.

์›๋ž˜ ์ฝ”๋“œ

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>
  );
profile
Frontend Developer, who has business in mind.
post-custom-banner

0๊ฐœ์˜ ๋Œ“๊ธ€