til

최종 프로젝트 17일 차 @참가하기 함수 리팩토링, 모달 트러블 슈팅

fpzmfks 2024. 11. 11. 23:38

오늘은 참가하기 함수를 리팩토링 했고, 그 와중에 발견한 모달의 오픈클로즈 트러블을 해결했다. 참가하기 기능은 모달로 만들었고, 이 모달이 총 3곳에서 사용되는 핵심 기능 중 하나이기 때문에 중요한 데다가 이것저것 고려할 것이 많다. 그 때문에 코드가 너무 길어져 버렸고 향후의 유지보수성을 위해서 우선적으로 리팩토링을 진행했다. 

참가하기 함수 리팩토링

참가하기 모달창의 핵심 기능은 4개로

  1. 파일업로드
  2. 유저프로필에서 디폴트 이미지와 닉네임 불러오기
  3. 원하는 프로필을 저장해서 참가하기(저장)
  4. 디폴트 프로필로 참가하기(넘어가기)

이다. 

 

 이 중 집중적으로 리팩토링을 해야 하는 것이 3번과 4번으로 기실 비슷한 구조의 함수이긴 하지만 세세한 처리를 다르게 해야하기 때문에  둘 다 로직이 충분히 길다. 

 

아래는 리팩토링하며 분리한 함수 중 하나로 이루어져야 하는 비동기 통신 위주로 모아둔 것이다. 

  1. 이미 멤버일 경우에는 insert가 아닌 업데이트(모집하기 페이지 용)
    1. 파일업로드
    2. 업로드한 이미지와 닉네임 update
  2. 모집마감 시 처리
  3. 참가하기
    1. 파일업로드
    2. 업로드한 이미지와 닉네임 insert
  4. 참가하기로 인해 모집 마감이 되면 모집 마감 상태로 전환

이렇게 최대한 분리한 코드만 해도 아래만한 길이가 되니 앞으로도 꼬박꼬박 리팩토링을 해야겠다는 마음이 정말 많이 들었다. 

const submitParticipation = async (
  nickname: string,
  selectImg: File | undefined,
  upload_profile_img: string,
  party_id: string,
  user_Id: string,
  setMessage: Dispatch<SetStateAction<string>>,
  deleteInviteMutation: UseMutationResult<void, Error, string, unknown>,
  invite_id?: string
) => {
  // 이미 멤버일 때 프로필 변경
  // 오너의 프로필 설정
  const isMember = await isMemberExist(party_id, user_Id);
  if (isMember) {
    const updateSuccess = await updateProfile(nickname, selectImg, upload_profile_img, party_id, user_Id);

    if (!updateSuccess) {
      alert('프로필 업데이트를 실패했습니다');
    }

    return;
  }

  // 파티 상태 확인하기
  const endCheck = await partySituationChecker(party_id);
  const memberCheck = await memberFullChecker(party_id);
  // 모집마감이거나 멤버가 찼지만 모집마감이 아닌 경우
  if (endCheck === '모집마감' || (memberCheck && endCheck !== '모집마감')) {
    if (memberCheck && endCheck !== '모집마감') {
      // 모집 마감으로 전환
      await memberFullSwitch(party_id);
    }
    setMessage('마감된 파티입니다');
    // 초대된 상태면 초대 목록에서 해당 초대를 삭제
    if (invite_id) {
      deleteInviteMutation.mutate(invite_id);
    }
    return;
  }

  // 참가하기
  const saveSuccess = await saveProfile(nickname, selectImg, upload_profile_img, party_id, user_Id);
  if (!saveSuccess) {
    alert('파티에 참가할 수 없습니다');
    return;
  }

  // 이 참가하기로 인해 인원이 가득 찼다면 파티 상태를 모집 마감으로 전환
  // 인원이 가득찼는지 확인
  const fullCheck = await memberFullChecker(party_id);
  if (fullCheck) {
    // 모집 마감 상태로 전환
    await memberFullSwitch(party_id);
  }
  // 초대된 상태면 초대 목록에서 해당 초대를 삭제
  if (invite_id) {
    deleteInviteMutation.mutate(invite_id);
  }
  setMessage('파티에 참가하신 걸 환영합니다!');
};

 

모달 트러블 슈팅

위와 같이 리팩토링 하며 참가하기와 참가취소 기능을 반복해서 테스트하는 동안 모달의 오류를 발견했다. 참가하기 성공 시 뜨는 '파티에 참가하신 걸 환영합니다!' 메세지가 제대로 뜨지 않고 닫히는 현상을 확인한 것이다. 또 이렇게 닫힌 모달은 같은 페이지에서 다른 모달을 열었을 때 다시 나타났다. 

 

여기저기 콘솔을 찍어보고 추측을 해봐도 왜 이런 현상이 일어나는지 실마리를 찾을 수 없어서 튜터님에게 상담을 해봤는데, 함께 디버깅을 하면서 코드를 한 줄씩 주석처리를 해보고 난 뒤에 실마리를 찾을 수 있었다. 콘솔을 찍거나 코드를 처음부터 읽어내려 가는 것 외에, 오늘 처음 해본 디버깅 방식이었다.

 

원인은 onSucces의 isMember의 invalidation 처리였는데, 내가 isMember의 값에 따라 모달 창이 포함된 버튼의 렌더링 여부를 다르게 하도록 설정해놓았기 때문에 isMember의 값이 변하면 모달 창이 렌더링되지 않는 문제가 있었던 것이다. 

 

일단 당장에 제법 치명적인 이슈를 해결하기 위해 css 요소인 hidden을 이용하여 아래와 같이 리팩토링 과정을 거쳤다. 두 개의 다른 기능을 가진 버튼이 isMember의 값에 따라 렌더링되는 게 아니라 css가 달라지도록 한 것이다. 하드코딩이 좀 심한 것 같다는 생각도 들었지만 추후에 또 리팩토링을 할 수 있을 것이라고 생각한다. 

{isMember ? (<Link
    className={chatOpenClose(partyData) === '시청중'
          ? 'flex btn-m w-full justify-center items-center'
          : 'flex disabled-btn-m w-full justify-center items-center'
    }
    href={`/chat/${partyData.party_id}`}
    onClick={(e) => {
      if (chatOpenClose(partyData) !== '시청중') {
        e.preventDefault();
        alert('시청시간 10분 전부터 입장하실 수 있습니다.');
      }
    }}
  >
    채팅하기
  </Link>):
  (<button onClick={() => setOpen(true)} className='btn-m w-full'>
    참여하기
  </button>
  <ParticipationButton
    party_id={partyData.party_id}
    party_situation={partyData.situation}
    openControl={open}
    setOpenControl={setOpen}
    isLogin={!!userId}
  />)}
<>
  <Link
    className={
      isMember
        ? chatOpenClose(partyData) === '시청중'
          ? 'flex btn-m w-full justify-center items-center'
          : 'flex disabled-btn-m w-full justify-center items-center'
        : 'hidden'
    }
    href={`/chat/${partyData.party_id}`}
    onClick={(e) => {
      if (chatOpenClose(partyData) !== '시청중') {
        e.preventDefault();
        alert('시청시간 10분 전부터 입장하실 수 있습니다.');
      }
    }}
  >
    채팅하기
  </Link>
  <button onClick={() => setOpen(true)} className={isMember ? 'hidden' : 'btn-m w-full'}>
    참여하기
  </button>
  <ParticipationButton
    party_id={partyData.party_id}
    party_situation={partyData.situation}
    openControl={open}
    setOpenControl={setOpen}
    isLogin={!!userId}
  />
</>