Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REFACTOR] API 중복 호출 방지 & 중복 투표 오류 해결 #402

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from

Conversation

rbgksqkr
Copy link
Contributor

@rbgksqkr rbgksqkr commented Nov 18, 2024

Issue Number

#401

As-Is

  • 네트워크 환경이 느릴 경우 또는 모바일 환경에서 빠르게 터치할 경우 API가 중복 호출되는 문제를 막아야 한다.
    • 특히 방 생성과 방 참여는 방의 사용자 수에 혼란을 줘서 UX를 해치며, 투표는 불필요한 에러 메세지를 띄운다.
  • useEffect에서 실행하는 API와 선택 완료 버튼을 눌렀을 때 실행하는 API 중복 호출을 막아야 한다.
    • 타이머가 완료되는 시점에 API 호출이 발생하고, 선택 버튼을 또 눌러서 중복 에러가 발생하는데 투표가 반영되어 사용자에게 혼란을 준다.

To-Be

  • 한 이벤트에서 API 중복 호출 방지 (throttle)
  • 중복 투표 오류 해결(투표 완료 상태를 관리하여 API 요청이 발생하자마자 disabled 처리)
  • 닉네임 페이지 리팩토링

Check List

  • 테스트가 전부 통과되었나요?
  • 모든 commit이 push 되었나요?
  • merge할 branch를 확인했나요?
  • Assignee를 지정했나요?
  • Label을 지정했나요?

Test Screenshot

쓰로틀링

throttle의 delay는 3초 이상일 때 32% 의 유저가 떠난다고 하는 구글 설문조사를 보고 3초로 결정하였습니다. 3초 안에 응답이 안올 경우 다시 요청을 보낼 수 있는데, 그런 상황이라면 서비스의 문제가 생겼다고 판단하였습니다.

따라서 3초 이내의 요청만 쓰로틀링이 걸려 과도한 API 요청을 막도록 개선하였습니다.

import { useRef } from 'react';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const useThrottle = <T extends (...args: any[]) => void>(
  func: T,
  delay = 3000,
): ((...args: Parameters<T>) => void) => {
  const isThrottledRef = useRef(false);

  return (...args: Parameters<T>) => {
    if (!isThrottledRef.current) {
      func(...args);
      isThrottledRef.current = true;
      setTimeout(() => {
        isThrottledRef.current = false;
      }, delay);
    }
  };
};

export default useThrottle;
const throttledVote = useThrottle(completeSelectionMutation.mutate);

중복 투표 오류 해결

타이머가 종료된 시점에 옵션만 누르고 선택 버튼을 누르지 않을 경우 투표가 되는 기능을 추가했었습니다.
하지만 타이머가 종료될 때 투표 API가 이미 호출되었는데, 화면이 넘어가는 응답값(isFinished:true)을 받기 전에 선택 버튼을 클릭하면 에러가 발생합니다. 서버에 이미 투표가 반영되었으니 중복 투표 에러가 뜨는데, 실제 결과에는 반영이 되므로 사용자 경험을 해치게 됩니다.

따라서 투표 API가 한번 호출되면 선택 완료 state값을 변경하여 disabled 처리가 되도록 수정하였습니다.
낙관적 업데이트처럼 미리 disabled 처리를 했는데, 이를 클라이언트 상태로 처리한 부분이 어색하긴 합니다! 근데 좋아요 기능처럼 이미 받고 있던 서버 응답값의 변화를 감지할 수 없어 이렇게 처리하였습니다. API 요청이 실패할 경우 다시 되돌리는 로직도 추가하였습니다!

닉네임 페이지 리팩토링

닉네임 페이지에 isJoinable이 false일 때 보여주는 에러 폴백 UI가 섞여 있었습니다. 저는 이를 잘못된 경로로 접근한 경우 또는 게임이 이미 시작한 경우로 판단하였습니다. 따라서 기존의 에러 폴백 UI를 보여주는 게 적절하다고 생각하여 isJoinable이 false일 때 CustomError를 반환하여 에러 바운더리에 걸리도록 구현하였습니다.

 const isJoinableRoomQuery = useQuery({
    queryKey: [QUERY_KEYS.isJoinable, roomUuid],
    queryFn: async () => isJoinableRoom(roomUuid || ''),
    select: ({ isJoinable }) => {
      if (isJoinable === false) {
        throw new CustomError({ errorCode: 'CAN_NOT_JOIN_ROOM', status: 400 });
      }
    },
    enabled: !!roomUuid,
  });

(Optional) Additional Description

handleMakeOrEnterRoom 을 handleAccessRoom으로 바꿨는데 바로 이해가 되는지 궁금해요!
그리고 취향일 수도 있겠지만 조건이 2개일 때 이벤트 핸들러 함수를 감싼 후 안에서 분기처리하는 것보다 props로 넘겨줄 때 분기처리 하는 게 직관적이다고 느꼈어요! 이것도 의견 주시면 좋을 것 같아용

함수 안에서 분기 처리

const handleAccessRoom = () => {
  if (isMaster) {
    handleCreateRoom();
  } else {
    handleEnterRoom();
  }
}

<Button onClick={handleAccessRoom} />

props로 넘겨줄 때 분기처리

<Button onClick={isMaster ? handleCreateRoom : handleEnterRoom} />

🌸 Storybook 배포 주소

https://woowacourse-teams.github.io/2024-ddangkong/storybook/

@rbgksqkr rbgksqkr added 🛠 fix 버그 ♻️ refactor 리팩토링 🫧 FE front end labels Nov 18, 2024
@rbgksqkr rbgksqkr self-assigned this Nov 18, 2024
@rbgksqkr rbgksqkr linked an issue Nov 18, 2024 that may be closed by this pull request
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🫧 FE front end 🛠 fix 버그 ♻️ refactor 리팩토링
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

[REFACTOR] API 중복 호출 방지
1 participant