본문 바로가기
🍀오늘도 삽질 중🍀/React.js

custom hook 제작해 리뷰 점수를 별점으로 만들기

by 매진2 2023. 8. 23.
728x90

이번 프로젝트에서 기능을 좀 더 고도화를 해볼까 하고 도전한 것이 별점이다. 처음에는 그냥 숫자로 나타내려고 하다가 아이콘도 써보고 싶고 별점을 구현해보고 싶어서 고민을 하다가 도전을 해보았다. 사실 그냥 숫자보다 별모양이 더 이쁘니까 ㅎㅎ

 

//custom hook 및 style이라고 적혀있는 코드 외에는 모두 별점이 있는 컴포넌트에서 작업했다.

 

아이콘 선택

svg를 쓸지 뭘 쓸지 고민을 하다가 폰트어썸을 쓰기로 결정을 했고 그 중 fastar, solid를 기본으로 진행하였다. 사실 초기엔 빈 별에서 꽉 찬 별로 진행하고 싶었는데 두개를 import 해오는게 안돼서 꽉찬별의 색을 바꿔주기로 했다.

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faStar } from '@fortawesome/free-solid-svg-icons';

 

custom hook 만들고 다른 컴포넌트에서 import 해오기

그리고 별점은 후기가 있는 프로젝트라면 어디서나 쓰일 것 같아서 재사용을 위해 custom hook을 만들어보기로 했다. 유저가 클릭한 별아이콘의 id값을 가지고 와 rate라는 state값을 변경 시켜주는 reactionStar라는 함수를 만들어준다. 그리고 기본 값인 5개의 회색별을 보여주기 위해 배열의 길이가 5인 starArr를 만들어준다. 또 전체리뷰의 평균 평점을 계산하기 위해 totalRating 함수를 만들었다. totalRating는 리뷰의 전체 데이터를 받아와서 평점을 모두 더한 뒤 리뷰의 갯수로 나누어 준다. makeZero 함수는 리뷰 등록 후 클릭되어있던 별 아이콘의 값을 초기화 시켜주는 함수이다. 마지막으로 필요한 값들을 return 해주어 다른 컴포넌트에서도 값을 불러와서 사용할 수 있게 만들어준다.

//custom hook

const useStarRating = () => {
  const [rate, setRate] = useState(0);
  const starArr = [1, 2, 3, 4, 5];

  const reactionStar = id => {
    setRate(id);
  };

  const totalRating = score => {
    let allStarRating = 0;
    let plusRating = 0;
    for (let i = 0; i < score?.length; i++) {
      allStarRating += Number(score[i].rating);
      plusRating = allStarRating / score.length;
    }
    return plusRating;
  };

  const makeZero = () => {
    setRate(0);
  };

  return { rate, starArr, reactionStar, totalRating, makeZero };
};

 

만들어둔 custom hook을 import 해온 뒤 구조분해할당해준다. 

import useStarRating from '../../hooks/useStarRating.js';

const { rate, starArr, reactionStar, totalRating, makeZero } = useStarRating();

 

totalRating 함수 사용

유저의 리뷰의 전체 평균 평점을 나타내기 위해 import로 가져온 것 중 평점에 관련된 함수인 totalRating을 불러온 뒤 데이터를 인자로 보내준다. 전체 댓글의 별점을 갯수로 나눠야해서 댓글의 전체 데이터를 인자로 보내주었다. 이제 totalRating함수에서 계산된 값이 화면에 출력될 것이다.

<Style.ProfileBold>평점</Style.ProfileBold>
<div>{totalRating(reviewData)}</div>

 

reactionStar 함수 사용

1,2,3,4,5라는 5개의 요소를 가진 배열인 starArr를 map을 돌려서 별 아이콘을 5개를 만들어준다. 그리고 custom hook에서 가져온 reactionStar라는 함수에 인자로 유저가 클릭한 별 아이콘의 값을 보내준다. 상태값인 rate와 아이콘 값인 star를 비교해 상태값이 더 높을때 true가 되고 boolean 값을 스타일컴포넌트로 보내주어 동적으로 색을 변경할 수 있도록 만들었다. 이제 유저가 3번째 별을 클릭하면 3개의 별이 노란색으로 바뀌게 된다. (회색 별을 5개 밑에 깔아주고 유저가 별을 클릭하면 그만큼 노란별이 회색별을 덮을수 있도록 구현하였다.)

 

작업을 할 때엔 star와 idx를 따로 생각했는데 지금보니 star가 idx+1인 값이어서 idx가 필요가 없다는 생각이 들었다. 더군다나 배열의 값이 추가 또는 삭제가 될 때 index값이 변경되어 원하지 않는 결과값이 나올 수도 있기 때문에 map에서 key값에는 index를 넣는걸 지양하라고 되어있어서 빼는 것이 좋을 것 같다. 다만 starArr이 하드코딩된 배열이라 이걸 재활용하기 쉽게 만드는 방법을 다시 생각해보는게 좋을 것 같다.

그리고 추천받은 방법은 array.from이나 array.fill을 사용해보라는 거였는데 조금 어려워서 생각을 많이 해봐야할 것 같다. 하지만 포기하지 않고 도전해봐야지!

 {starArr?.map((star, idx) => {
    return (
      <Style.AllStar star={star <= rate} key={idx}>
      <FontAwesomeIcon
       icon={faStar}
       onClick={() => reactionStar(star)}
       />
      </Style.AllStar>
      );
    })}

 

styled-components로 값을 넘겨준 star 라는 props는 star 값이 유저가 클릭한 rate라는 값보다 작거나 같을 때 노란색, 크면 회색으로 바뀌도록 구현이 되어있다. 이때 아이콘의 색을 변경해야해서 조금 어려웠었지만 동기의 도움으로 금방 해결할 수 있었다.

//style.js

AllStar: styled.div`
    display: flex;
    svg {
      font-size: 1.3em;
      path {
        color: ${props => (props.star ? props.theme.mainColor : 'lightgray')};
        cursor: pointer;
      }
    }
  `,

 

코드리뷰를 받으면서 hook을 더 재활용할 수 있게 만드는게 좋을 것 같다는 말을 들었다. 가령 최대 점수를 4점, 5점, 3점 이런식으로 유동적으로 변경할 수 있으면 더 좋지 않겠냐는 의견이었고 사실 최대점수는 홈페이지 당 1개겠지만 여러 프로젝트에서 사용할 수 있도록 만들려면 바꿔두는것도 좋겠다는 생각이 들어서 리팩토링을 하면서 해봐야겠다.

 

728x90