✨ 기억보다 기록을/트러블슈팅

IceCraft - 메인페이지 리팩토링 대공사 모음집

서카츄 2024. 7. 23. 01:43

 

 

[Fix #333] - 메인페이지 오류들 수정, 타입봇 추가 by seokachu · Pull Request #341 · ice-craft/ice-craft

📌 관련 이슈 closed #333 Task TODOLIST 메인페이지에서 방 리스트 계속 리랜더링 되는 오류 수정 메인페이지 함수 클릭 이벤트들 함수만 모아둔 컴포넌트 수정 메인페이지 방 만드는 모달창 수정 후

github.com

 

 

우리 프로젝트에서 BaaS로는 supabase를 사용하고 있는데,

현재 데이터가 5번이나 계속 불러와지고 있었다.

 

 

수파베이스 코드

export const getUserInfo = async () => {
  const { data } = await supabase.auth.getUser();
  return data.user;
};

 

 

 

여기 data에 유저 정보가 담겨져 있는데, console.log를 찍어보니 이상하게 계속 불러와지는 현상이 일어나고 있었다ㅠㅠ

계속 리랜더링 문제인가 싶어 타고타고 다 삭제하고 콘솔로그를 찍어보았는데

내가 훅으로 만들어서, 사용해서 쓰는 곳 마다 메인페이지에서 계속 훅에서 랜더링되어 불러오고 있었다.

알고보니 수파베이스에서는 정상적으로 한번만 불러오고 있었음(...) 매우 정상적임...

 

현재 이거때문에 메인페이지에서 계속 error메세지도 5번연속으로 떴고,

방 리스트 불러오는 return문도 연달아 5개연속으로 불러오고 있었다..;

근본적인 문제를 해결해야 다른 곳도 해결되는 상황이었다. ㅜㅜ

 

그래서 계속 고민하면서 찾아보다가, 다시 처음부터 돌아가서 하나하나씩 콘솔로그로 파악하기 시작했다.

 

태초마을로 돌아가자,  처음 발생하는 문제의(?) 코드

import { Tables } from "@/types/supabase";
import { useEffect } from "react";
import useSocketOn from "./useSocketOn";
import { socket } from "@/utils/socket/socket";
import { useConnectActions, useRoomsCurrent } from "@/store/connect-store";
import useLoading from "./useLoading";

const useGetRoomsSocket = () => {
  const rooms = useRoomsCurrent();
  const { setRooms } = useConnectActions();

  const mainSockets = {
    enterMafia: (item: Tables<"room_table">[]) => {
      setRooms(item);
      stopLoading();
    }
  };
  useSocketOn(mainSockets);

  useEffect(() => {
    socket.connect();
    socket.emit("enterMafia");
  }, []);

  return { rooms, setRooms };
};

export default useGetRoomsSocket;

 

여기서 소켓에서 연결하고 전송할때, enterMafia를 한번 실행하게 만들고 있었는데,

서버에서는 두번이나 작동되고 있었다.

그 이유는 알고보니 메인페이지에서 useGetRoomsSocket훅을 메인페이지에서 한번,

다른 자식 컴포넌트들 에서 훅을 두번 불러오게 되니, 콘솔로그도 여러번 찍히게 된 것이었다.

 

심지어 처음 로딩 될때는 빈배열로 뜨다가, 소켓에서 데이터 들고와서 두번 불러와짐...

 

 

문제1. 서버에서 enterMafia가 두번 호출된다.

  //NOTE - 소켓 연결
  useEffect(() => {
    socket.connect();
    socket.emit("enterMafia");
  }, []);

훅에서 연결하는 부분(enterMafia)가 서버에서 두번 호출하고 있어서,

연결하는 부분을 메인페이지로 옮기고, 한번만 호출되도록 설정하였다.

 

 

문제2. 방 리스트가 여러번 호출되고 있음.

이 부분은 hook을 쓰고있는 메인페이지와 자식컴포넌트들에 같은 훅을 호출하면 여러번 호출이 되었는데,

hook안에 있는 room을 불러오는게 아니라, 이미 전역상태로 관리하고 있는 방 리스트를 불러와야 했다.

 

 

이전코드(소켓에 있는 setRooms를 불러왔었음)

  const { setRooms } = useJoinRoomSocket();

 

바뀐 코드 (전역상태에 있는 setRooms를 불러옴)

  const { setRooms } = useConnectActions();

 

진짜 간단한거였는데 왜 이 문제를 지금 발견하였는가;;; 진짜 바보다...^_ㅠ....

 

 

문제3. 클릭이벤트들을 모아둔 컴포넌트를 불러와서 사용할 때 마다, 방 리스트가 다시 불러오는 현상

위와같이 메인페이지에서 콘솔로그를 찍었는데, 클릭이벤트들을 모아둔 함수 컴포넌트를 불러오면

또 다시 방 리스트들을 여러번 불러오고 있었다,,,;;

 

 

 

다시 console.log로 추적 시작!

 //NOTE - 사용자 로그인 여부
  useEffect(() => {
    const checkUserInfo = async () => {
      try {
        const userInfo = await checkUserLogIn();
        if (userInfo) {
          setUserId(userInfo.id);
          setUserNickname(userInfo.user_metadata.nickname);
        }
      } catch (error) {
        toast.error("로그인 여부를 확인해 주세요.");
      }
    };
    checkUserInfo();
  }, []);

 

이 부분을 클릭이벤트들만 모아둔 함수들에 넣어놨는데,

사용자가 로그인을 했는지, 안했는지 여부에 따라

setUserId, setUserNickName에 상태변경이 일어나고,

다시 메인페이지에서 쓰고 있기때문에 방 리스트가 두번이 호출 되고 있는 것이었다.

 

 

문제 해결

그래서 클릭이벤트 함수들만 모아둔 곳은 클릭이벤트 함수들만 모아두게 만들어놨고,

위에서 상태 변경이 일어나고 있는 코드는 header에 있는 nav부분에 쓰기 때문에 nav컴포넌트로 옮겨주게 되었다.

 

 

그로인해 클릭이벤트를 진행했을 때, 사용자가 비로그인시, 방을 입장 못하게 막아놨어야 했는데

클릭이벤트 함수를 모아둔곳에 사용자 로그인 여부를 확인했다.

//NOTE - 클릭시 로그인 안한 유저 처리
  const loginErrorHandler = async (emitCallback: (userId: string, userNickname: string) => void) => {
    setLoading(true);
    try {
      const userInfo = await checkUserLogIn();
      if (!userInfo) {
        toast.info("로그인 후 입장 가능합니다.");
        return;
      }
      if (!isGoInClick.current) {
        isGoInClick.current = true;
        emitCallback(userInfo.id, userInfo.user_metadata.nickname);
      }
    } catch {
      toast.error("로그인 확인 중 오류가 발생했습니다.");
    } finally {
      isGoInClick.current = false;
    }
  };

  useEffect(() => {
    return () => setLoading(false);
  }, []);

 

우리는 클릭이벤트가 일어나는 메인페이지에서 총 3개의 버튼이 있는데 (게임 시작, 빠른 입장, 방 리스트들 입장버튼)

그 부분에 계속 로그인, 비로그인 여부를 확인했어야 했기 때문에 계속 같은 코드를 반복하게 되어

하나의 함수로 비로그인&로그인 체크여부는 콜백함수를 사용하여 함수 하나로 묶게 되었다.

 

처음 사용자가 클릭했을 시, loginErrorHandler 함수를 거쳐 사용자가 로그인이 되었으면,

소켓통신을 통해 입장할 수 있도록 만들어 두었다.

그리고 수파베이스에 있는 데이터 checkUserLogIn 함수를 받아와서 로그인 입장 여부를 확인했고,

그 데이터를 받아와 userId와 userNickname정보를 콜백함수로 함께 보내주었다.


그리고 finally가 되자마자 loading(false)로 설정해놨었는데

서버에서 받아오는 딜레이보다 Loading이 빨리 끝나버려서 UX적으로 별로라고 생각이 들었다.

그래서 useEffect안에 넣어 페이지가 이동시, 

즉, 언마운트 되어 페이지가 이동이 끝나면 setLoading(false)를 실행되게 하여 조금 더 사용자 UX에 맞게 수정하게 되었다.

 

 

클릭이벤트 함수들

//NOTE - 방 리스트 입장하기
  const joinRoomHandler = async (item: Tables<"room_table">) => {
    await loginErrorHandler((userId, userNickname) => {
      setRoomId(item.room_id);

      setIsEntry(true);
      socket.emit("joinRoom", userId, item.room_id, userNickname);
    });
  };

  //NOTE - 메인페이지 visual에서 게임시작 버튼 클릭시(추후 마피아 & 노래맞추기 조건 추가)
  const gameStartHandler = () => {
    loginErrorHandler((userId, userNickname) => {
      setIsEntry(true);
      socket.emit("fastJoinRoom", userId, userNickname);
    });
  };

  //NOTE - 빠른 입장 (랜덤 방 입장)
  const fastJoinRoomHandler = () => {
    loginErrorHandler((userId, userNickname) => {
      setIsEntry(true);
      socket.emit("fastJoinRoom", userId, userNickname);
    });
  };

 

userNickname과 userId를 전역관리로 받아와서 써도 됐지만, 이미 수파베이스에서 유저 정보를 받아오고 있기 때문에

전역관리한 userNickname과 userId를 사용하지 않고,

수파베이스에서 받아오는 유저정보의 데이터를 콜백함수로 전달하여 작동되도록 고민하며 코드를 짜 보았다.

이렇게 짠 이유는 일일이 클릭이벤트마다 전역관리한 유저정보를 받아와서 쓸 일이 없다고 생각하기 때문이었다.

데이터를 한번 불러와서 같은 코드를 반복하는 loginErrorHandler 함수에 유저정보를 한번 넣어주면

클릭이벤트마다 전달해 줄 수 있어서 더 활용성 있다고 생각해서 이렇게 코드를 짜게 되었다.

 

 

 

문제4. 버튼 클릭 시, 일부 버튼들 Loading안 나오는 문제

처음엔 loading을 전역상태로 관리하지 않고, 훅으로 관리하고 있었는데, 그로인한 문제들은

 

1. 훅은 독립적으로 작동하기 때문에 클릭이벤트가 발생 시, 상태공유가 안되어 메인페이지에 있는 loading만 뜨고 있었다.

2. 메인페이지 안에 있는 자식 컴포넌트들은 클릭이벤트를 진행하려고 하면 뜨지 않아 따로 자식컴포넌트 마다 loading훅을 불러와야 했다.

 

 

여기서 2가지 선택에서 고민을 하게 되는데...

전역상태 관리를 해고, 상태 공유를 하여 메인페이지에만 로딩페이지를 넣을까?

loading 훅을 이미 만들어놨는데 자식 컴포넌트마다 로딩페이지를 불러오는게 나을까?

 

그래서 나의 선택은....?

으로 진행하지 말고, 전역상태관리로 변경하자!

자식 컴포넌트마다 loading을 사용한다고 가정했을 때, 지금은 프로젝트 규모가 크지 않기 때문에

자식컴포넌트마다 일일이 불러와서 사용해도 됐지만

프로젝트 규모가 커지고 있다면? 메인페이지에 기능들이 많이 생긴다면?

자식컴포넌트들을 찾아가 로딩페이지를 하나하나씩 불러오는것이 비효율적이라고 생각 들었기 때문에

loading페이지는 전역상태로 관리하는게 좋겠다는 생각을 하게되어 전역상태로 변경하게 되었다!

 

 

그래서 메인페이지는 대공사중...! 

그렇게 메인페이지는 대공사를 진행했고 현재 큰 오류 없이 잘 작동되는 중이다.

but, 아직 부족한 부분들이 보여서 채워나가야 할 것 같다. 리팩토링은 끝이 없는 것 같은 느낌이다.

부트캠프가 끝나고 페이지 전체를 갈아엎으며 다시 팀원들과 업무 분배를 나누고, 

메인페이지를 맡아 리팩토링 과정을 진행했는데 계속 고민하면서 코드를 짜다보니

지금은 글 남기는 내용은 함축적으로 작성했지만, 중간 과정에서 몇번이고 코드를 갈아엎게 되면서 많이 배우게 된 것 같다. (ㅠㅠ)

진짜진짜 꾸준히!!! 열심히 하는것도 좋지만 코드는 애정을 가지고

몇번, 꾸준히 보면서 어떻게 짜면 더 좋은 코드가 될지 생각하며 짜는게 중요하다고 생각했다.

꾸준히 공부해서 발전하는 내가 되어야지.... 

 

그리고 우리 팀원들....

항상 부족한 리더 잘 따라와줘서 늘 고마워요🥲