[17일차] 실시간 인기차트 구현하기(json-server)

h-a-n-a·2023년 8월 30일

💫Ed Sheeran

목록 보기
17/24

Ed Sheeran 명곡들을 유저가 투표할 수 있는 실시간 인기차트 페이지를 구현하려고 한다!

따로 API나 내가 만든 진짜 서버는 없기에, json-server 라이브러리를 이용해 간단한 시뮬레이션 정도 할 수 있는 REST API Mock Server를 이용하려고 한다.

공식문서를 참고해 설치하고,

yarn add json-server

프로젝트 안에 data.json 을 만든다.(공식문서에 있는 예제코드 그대로 복붙)

{
  "posts": [
    { "id": 1, "title": "json-server", "author": "typicode" }
  ],
  "comments": [
    { "id": 1, "body": "some comment", "postId": 1 }
  ],
  "profile": { "name": "typicode" }
}

나중에 data.json은 다시 내가 필요한 형식으로 바꿔줬다.

그런 다음엔 아래 명령어를 터미널에 입력해

json-server --watch data.json

JSON SERVER를 실행하면 된다. 매번 저렇게 긴 명령어를 치면 번거롭기 때문package.json에서 수정 가능!

//package.json
{
 "name": "app",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview",
    "start": "json-server --watch data.json --port 3000"
  },...
}

인기차트에서 구현하고 싶었던 건, 첫째로, 유저가 좋아요를 클릭하면 좋아요 숫자가 1 올라가고, 다시 한 번 클릭하면 해제되는 거였다.

둘째로, 좋아요 숫자에 따라 실시간으로 순위가 변동될 수 있도록 하고 싶었다.

마지막으로, 새로고침해도 상태는 유지되어야 했다.

곡 좋아요 누르면 +1

처음 작성했던 코드는 다음과 같았다.

function Chart() {
  const [songs, setSongs] = useState<Song[]>([]);
  const [isClicked, setIsClicked] = useState<number | null>(null);
  const handleLikeClick = async (
    songId: number,
    title: string,
    img: string,
    album: string
  ) => {
    setIsClicked((prevSongId) => (prevSongId === songId ? null : songId));
    const updatedSongs = songs.map((song) => {
      if (song.id === songId) {
        return { ...song, likes: song.likes + 1 };
      }
      return song;
    });

    setSongs(updatedSongs);

    try {
      await updateLikes(
        songId,
        updatedSongs.find((song) => song.id === songId)?.likes || 0,
        title,
        img,
        album
      );
    } catch (error) {
      console.error('Error updating likes:', error);
    }
  };

  useEffect(() => {
    getSongs()
      .then((data) =>
        setSongs(data)
      )
      .catch((error) => {
        console.error('Error fetching songs:', error);
      });
  }, []);

  return (
    <S.StyledTableContainer>
      <Table>
        <TableHead>
          <TableRow hover={true}>
            <TableCell>순위</TableCell>
            <TableCell align='right'>곡 정보</TableCell>
            <TableCell align='right'>앨범</TableCell>
            <TableCell align='right'>좋아요</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {songs &&
            songs.map((song, index) => (
              <TableRow key={song.id}>
                <TableCell>{index + 1}</TableCell>
                  {isClicked === song.id ? (
                    <GoHeartFill
                      onClick={() =>
                        handleLikeClick(
                          song.id,
                          song.title,
                          song.img,
                          song.album
                        )
                      }
                      style={{ cursor: 'pointer' }}
                    />
                  ) : (
                    <GoHeart
                      onClick={() =>
                        handleLikeClick(
                          song.id,
                          song.title,
                          song.img,
                          song.album
                        )
                      }
                      style={{ cursor: 'pointer' }}
                    />
                  )}
                  {song.likes}
                </TableCell>
              </TableRow>
            ))}
        </TableBody>
      </Table>
    </S.StyledTableContainer>
  );
}

export default Chart;

그러자 곡 A의 좋아요를 누르면, 곡 B의 좋아요가 해제되었다. 모든 곡을 하나의 isClicked에 담아 관리해서 나타나는 문제였다.

그래서 상태를 하나 더 추가해서 각각의 노래에 대한 하트 클릭 상태와 좋아요 수를 관리하기로 했다.

 const [clickedSongs, setClickedSongs] = useState<number[]>([]);
  const [songLikes, setSongLikes] = useState<number[]>([]);

그리고 handleLikcClick 함수를 수정해줬다.

 const handleLikeClick = async (
    songId: number,
    title: string,
    img: string,
    album: string
  ) => {
    if (clickedSongs.includes(songId)) {
      // 이미 클릭한 노래의 하트를 다시 클릭하면 클릭 상태를 해제
      setClickedSongs((prevClickedSongs) =>
        prevClickedSongs.filter((id) => id !== songId)
      );

      // 좋아요 수 -1
      setSongLikes((prevSongLikes) =>
        prevSongLikes.map((likes, index) =>
          index === songId ? likes - 1 : likes
        )
      );
       } else {
      // 클릭하지 않았던 노래의 하트를 클릭하면 클릭 상태 추가
      setClickedSongs((prevClickedSongs) => [...prevClickedSongs, songId]);

      // 좋아요 수 +1
      setSongLikes((prevSongLikes) =>
        prevSongLikes.map((likes, index) =>
          index === songId ? likes + 1 : likes
        )
      );
    }

좋아요 숫자에 따라 순위 변동

sort 함수를 이용해 setSongs의 songs를 다시 만들어줬다

getSongs()
      .then((data) =>
        setSongs(data.slice().sort((a: Song, b: Song) => b.likes - a.likes))
      )

그리고 마지막으로 새로고침해도 계속 상태 유지하도록 하는 기능은 생각보다 시간이 좀 걸렸다ㅠㅠ

2탄에서 계속..!

profile
하루하루가 연습이니 내일은 더 강해질 겁니다

0개의 댓글