EtoC

제품상세 페이지 만들기 본문

FrontEnd/React

제품상세 페이지 만들기

게리드 2023. 8. 30. 21:31

제일 해보고 싶었던 상품 상세페이지!

주말동안에 개구리 css문제도 다풀어봤다고?!
푸터를 끝내고 처음으로 내가 꾸미고 싶은대로(이솝을 참고해서이지만..) 할수있어 너무 설렜다.

일단 데이터는 이렇게 주고 받기로 하였다.


구현 과정

1.  map 함수로 데이터 처리

이미 레이아웃을 만들때 상수데이터를 사용해서 만들어두어서 목데이터만 추가로 map을 돌려서 만들어보았다.
그렇게 자신감 넘치게 npm start를 쳤다.
그런데

 

 

앗 맵함수 잘못썼다

 

이렇게 작성한 이유는 타입명 옆에 값들이 map함수로 착착 들어갈거라 생각해서..

하지만 현실은 타입(주르륵), 값(주르륵)

'map함수를 돌려서 바로바로 옆에 띄우는 방법은 없는걸까?' 고민해보았으나 답이 보이지않았다.

하나씩 해보자며 맵함수를 분리하니 아래 이미지와 같이 만들어졌다.


 

분리하고 계속 보니 뭔가 알것 같았다.

제품 상세 목록을 div 태그나 li태그로 묶어서 css를 다르게 주면 되지않을까?

div 태그로 감싼뒤 flex를 주어서 상수데이터 옆에 목데이터가 위치하도록 만들었다.

  {product.map(el => (
        <div className="productBox" key={el.id}>
          <div className="itemImageBox">
            <img
              src={el.image_url}
              alt="productImage"
              className="productImage"
            />
          </div>
          <div className="productDescription">
            <h1 className="productName">{el.name}</h1>
            <p className="mainDescription">{el.description}</p>
            <ul className="descriptionBox">
              <li className="usingType">
                <div>TYPE</div>
                <p className="itemUsingType">{surfaceType}</p>
              </li>
              <li className="usingType">
                <div>SIZE</div>
                <p className="itemUsingType">{newSize}</p>
              </li>
              <li className="usingType">
                <div>THICKNESS</div>
                <p className="itemUsingType">{thick}</p>
              </li>
              <li className="usingType">
                <div>WEIGHT</div>
                <p className="itemUsingType">{el.weight} kg</p>
              </li>
            </ul>

 

 

오예 원하는대로 바뀌었다!

근데 문제는 내가 레이아웃을 짜면서 map함수로 상수데이터들을 다 만들어뒀는데,

이후의 작업에서도 같은 문제가 발생할 가능성이 높다는것..


2.  버튼 컴포넌트 만들기

데이터를 잘 맞춰두고 이제 수량을 조절할 버튼을 만들차례가 왔다.
수량조절버튼은 장바구니에서도 쓸 꺼라서 컴포넌트를 분리해야겠다 생각했다.

import React, { useState, useEffect } from 'react';
import './Count.scss';

const Count = ({ countNumber, setCount, isDisabled }) => {

  useEffect(() => {
    setInternalCount(Number(countNumber) || 1);
  }, [countNumber]);

  const decrease = () => {
    if (count <= 1) {
      return;
    } else {
      const newCount = count - 1;
      setInternalCount(newCount);
      setCount(newCount);
    }
  };

  const increase = () => {
    const newCount = count + 1;
    setInternalCount(newCount);
    setCount(newCount);
  };

  return (
    <div className="count">
      <div className="countInput">
        <button onClick={decrease}>-</button>
        <div className="countInputText">{count}</div>
        <button onClick={increase} disabled={isDisabled}>
          +
        </button>
      </div>
    </div>
  );
};

export default Count;

분리한건 좋았는데 버튼 디자인이 촌스럽대서 여러 사이트를 돌며 버튼 모양을 찾아다녔다.

그러다 마음에 쏙 드는 디자인을 찾음.

좋아 너로 정했다.


3.  버튼에 제한 주기

일단 제품 상세페이지에는 물건이 하나만 들어오니까 product라는 배열 안에 1개의 값밖에 없을테니
product[0]의 무게와 가격에 변경된 갯수를 곱하여 합산된 무게와 가격을 구하였다.
그리고 무게제한을 초과할 경우 경고말을 띄우는 함수를 만들었다.

 const [countNumber, setCount] = useState(1);
  const [product, setProduct] = useState([]);
  const { name } = useParams();

 if (product && product.length > 0) {
    totalWeight = (product[0].weight * countNumber).toLocaleString();
    totalPrice = (product[0].price * countNumber).toLocaleString();
  }

  const showAlert = product ? product[0]?.weight * countNumber > 1000 : 1;

만들고나서 무게제한 뜨는거보고 뿌듯해서 팀원들 보여주고 신나했는데 문제가 생겼다.


어려웠던점

1. 숫자로 들어오는 값 사용하기

이제 틀을 다잡고 데이터를 받아왔는데 의문이 들었다.


'서브카테고리 아이디를 사용해서 그안의 값을 어떻게 꺼내써야하지..?'
sub_category name이 사이즈와 두께를 포함하고있어 값을 받야하하는데
1만 들어와서 1에해당하는값을 어떻게 꺼내써야할지 모르겠었다.
프론트에서 이걸 어떻게 쓰지라는 생각이들었고,
멘토님께 질문드리니 이 경우에는 데이터를 뽑아 쓸수 없어서 변수로 선언하고 사용해야한다고 했다.

const surface_type = ['Matt', 'Hard Matt', 'Soft Matt', 'LappaTo', 'Glossy'];
const sub_categories = [
    '600x600x10mm',
    '600x600x20mm',
    '600x1200x11mm',
    '600x1200x20mm',
    '400x800x11mm',
    '300x600x9mm',
    '200x600x9mm',
    '300x300x9mm',
    '200x400x9mm',
  ];

 

이러면 백에서 테이블을 만든의미가 있나 싶지만, 일단 이렇게 선언해서 사용했다.
급할때는 이렇게 사용할수도 있겠구나싶었다.

이제 여기서 사이즈와 두께로 나누어서 변수로 선언하면 끝!


2.  내 함수 이상해

서브카테고리 값에서 필요한데이터만 잘라내어 쓰는것은 어렵지않았다.

그런데 웬걸 자꾸 undefined가 뜨면서 에러가 났다.

원인은 재랜더링되면서 값을 찾지못한 상태로 랜더링이되서 undefined가 뜨는 것이였다.

책에서 봤던 옵셔널 체이닝을 사용하니 제대로 작동하였다.

(early return이라는것도 있던데 나중에 공부해봐야겠다.)

const findSize = subCategoryId => {
    const foundSizes = sub_categories.filter(
      (size, index) => index + 1 === subCategoryId
    );
    return foundSizes.length > 0 ? foundSizes[0] : '';
  };

  const subCategoryId = product[0]?.sub_category_id;
  const size = findSize(subCategoryId);

  const newSize = size.replace('mm', '').split('x10');

  const thick = size.split('x')[2];

  const findSurfaceType = surfaceTypeId => {
    const foundTypes = surface_type.filter(
      (type, index) => index + 1 === surfaceTypeId
    );
    return foundTypes.length > 0 ? foundTypes[0] : '';
  };

  const surfaceTypeId = product[0]?.surface_type_id;
  const surfaceType = findSurfaceType(surfaceTypeId);

근데 왜 이렇게 지저분하지..
split 이렇게밖에 못써? 뭔가 더 깔끔하게 할 수 있을거같은데
일단 시간이없으니 이것도 리펙토링때 하기로..


4. 인가받지 못한 유저 처리

가장 쉬웠지만 신기했던 부분.
내가 토큰을 보내본적은 있지만 어떻게 사용하는지는 정확히 알지 못했다.

post요청시 헤더에 토큰이없으면 res로 에러코드나 메세지가 오는데 이 메세지가 성공일때는 카트페이지로,
에러코드를 받았을때 경고창을 띄우고 로그인페이지로 보내는게 신기했다.

const token = localStorage.getItem('TOKEN');

  const checkToken = (e, product) => {
    if (!token) {
      e.preventDefault();
      navigate('/users/signin');
      alert('로그인을 먼저 진행해 주세요.');
    } else {
      createCart(product, token);
    }
  };

  const createCart = () => {
    fetch('http://10.58.52.156:3000/carts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json;charset=utf-8',
        authorization: token,
      },
      body: JSON.stringify({
        productId: product[0]?.id,
        quantity: countNumber,
      }),
    }).then(res => {
      if (res.status === 200) {
        navigate('/carts');
      } else if (res.status === 400) {
        navigate('/users/signin');
      }
    });

 

요렇게 완성했다 ㅎㅎ

 

 

상품상세페이지를 만들면서 css와 map함수애대한 이해가 정말 많이늘었다.

가장많이 배우고 가장 재미있었던 담당 파트였다.

 

2023-07-02