react query 콜백 지옥 해결

하야·2023년 11월 4일


리뷰레인저 (RR)

프로그래머스 최종 프로젝트에서 우리 팀은 프론트 3명, 벡엔드 3명으로 구성되었다.
다들 열심히하시고, 실력자들이셔서 존경스럽고 많이 배우고 싶다.

우리 팀의 프로젝트명은 '리뷰레인저'로 피어 리뷰를 효과적으로 관리하는 플랫폼이다.
우리 팀의 궁극적인 목표 즉, 미션(mission)은 프로젝트에 참여하는 모든 구성원의 리뷰 문화 활성화이다. 피어 리뷰(동료 평가)뿐만 아니라 더욱 확장하여 모든 리뷰를 다루는 것을 최종 목표로 하고 있다.


MSW

'리뷰레인저' 프로젝트를 진행하는 중 react query를 사용하여 서버와의 데이터 관리 및 비동기 처리를 다루었다.

아직 벡엔드 API가 완성되지 않아, 일시적으로 msw(Mock Service Worker)를 사용하였다. msw는 실제 서버 API 호출을 대체하여 가짜(mock) 데이터를 응답으로 반환하고 클라이언트와 서버 간의 통신을 가짜 데이터로 대체할 수 있다. msw를 사용하여 실제 API를 적용할 때 필요한 query hook들을 미리 만들어 둘 수 있었고, 테스트를 통해 비동기 처리가 잘 동작하는지 확인할 수 있어 편리했다!

msw를 구현하는데 하루 이상을 사용하였지만(논의 및 분배까지 2-3일 소요), 충분히 가치 있다고 느꼈다.

사실 개발 일정이 넉넉하지 않은 시점에서 '꼭 필요한 기능이냐'라고 물으면, 그건 아니다. 테스트 데이터가 필요하면 JSON으로 빠르게 mock 데이터를 만들고 테스트를 해볼 수도 있을 것이다. 하지만 특정 파일 또는 로직에서 mock 데이터의 맥락을 알아야하고, 비동기 처리를 위한 함수 구현이 어려울 수 있다.

msw를 사용하면 클라이언트 단에서 서버 통신에 필요한 함수들을 미리 구현해 둘 수 있고, 비동기 처리 테스트도 해볼 수 있어 너무 좋았다! (약간 내가 벡엔드까지 자유자재로 다루는 느낌ㅎㅎㅎ...🫣)
실제 API가 완성되면, mock 데이터와 handler만 지워주면 되어 매우 편리하다.


react query 콜백 지옥

react query를 사용하여 비동기 처리하는 중, 말로만 듣던 콜백 지옥에 빠져버렸다...!
회원가입 페이지에서 '회원가입 완료' 버튼을 클릭하게 되면 이메일 중복 검사가 실시되고, 이메일 중복 검사를 통과하면 이름 중복 검사가 실시되고, 이름 중복 검사가 통과된 후에 회원가입 처리가 진행된다.

회원가입 완료 버튼 클릭 -> 이메일 중복검사 -> 이름 중복검사 -> 회원가입 처리

즉, 회원가입 버튼 클릭 이벤트로 3가지 비동기 통신이 이루어지는 것이다.
사실 근본적으로 이메일 중복 검사와 이름 중복 검사가 꼭 종속이 되어야 할 필요는 없지만, 팀원들과 함께 정한 로직이라 우선 구현하였다.

useMutation을 사용하여 비동기 처리를 진행하였고, 아래가 내가 짠 코드이다.

const { mutate: signUp } = useSignUp()
const { mutate: checkDuplicatedEmail } = useCheckDuplicatedEmail()
const { mutate: checkDuplicatedName } = useCheckDuplicatedName()

const handleSignUpButtonClick = () => {
  ...
  checkDuplicatedEmail(
    { email },
    {
      onSuccess: ({ data }) => {
        if (data.success) {
          checkDuplicatedName(
            { name },
            {
              onSuccess: ({ data }) => {
                if (data.success) {
                  console.log('회원가입 완료!')
                  signUp(
                    { email, name, password },
                    {
                      onSuccess: () => navigate('/login'),
                    },
                  )
                } else {
                  setNameFailMsg('이미 존재하는 이름이라구.')
                }
              },
            },
          )
        } else {
          setEmailFailMsg('이미 존재하는 이메일이라구.')
        }
      },
    },
  )
}

중복 검사 api 통신은 response로 {success: boolean}이 반환된다.
중복 검사가 통과되면 true, 중복이면 false 값을 가진다.
로직은 이메일 중복 검사를 한 후, 통신에 성공하여 응답으로 받은 data.successtrue이어야 이름 중복 검사가 시작된다. 즉, checkDuplicatedEmail 비동기 통신 후, 데이터 결과 값에 따라 다음 로직이 진행되어야 해서 콜백 함수를 중첩으로 사용하게 되었다.
async, await을 사용하여 동기식으로 동작할 수 있게끔 만들고 싶었지만, mutate는 Promise 객체를 반환하지 않아 사용할 수 없었다.
정말이지 가독성 떨어지는 말도 안되는 코드를 적어 놓고, 그 순간엔 기능이 잘 동작한다는 것에 만족하였다.🫠

react query를 이렇게 사용하는 것이 절대 아닐 것이란 생각에 더욱 알아보았고, mutate가 아닌 비동기 작업을 수행하고 Promise 객체를 반환하는 mutateAsync를 알게 되었다.
Promise를 반환하니 async, await을 사용할 수 있었고, 콜백 지옥에서 벗어날 수 있었다!

const { mutateAsync: signUp } = useSignUp()
const { mutateAsync: checkDuplicatedEmail } = useCheckDuplicatedEmail()
const { mutateAsync: checkDuplicatedName } = useCheckDuplicatedName()

const handleSignUpButtonClick = async () => {
  try {
    const { data: emailData } = await checkDuplicatedEmail({ email })
      if (!emailData.success) {
        setEmailFailMsg('이미 존재하는 이메일이라구.')
        return
      }

    const { data: nameData } = await checkDuplicatedName({ name })
      if (!nameData.success) {
        setNameFailMsg('이미 존재하는 이름이라구.')
        return
      }

    await signUp({ email, name, password })
    navigate('/login')
  } catch (e) {
    console.error('axios 통신 오류', e)
  }  
}

코드가 매우 깔끔해졌고, early return을 통해 복잡한 조건문을 간결화하였다!!!!👍

사실 한 글에서 "대부분의 경우 우리는 mutate를 사용하는 것이 유리하다." 라고 쓰여있어 자세히 알아 볼 생각도 없이 무조건 mutate만 생각했었다.
레퍼런스를 참고할 때는 내용을 꼼꼼히 읽고 사용하는 방법과 이유를 잘 파악해야겠다고 생각되었다.

profile
알파카의 코딩 생활

0개의 댓글