logo

DowanKim

6. 카카오로그인 기능 코드리뷰 피드백 반영

2025년 9월 20일

이게머니

기존 카카오콜백페이지 코드에는 너무나 많은 내용이 useEffect 안에 길게 적혀 있습니다.

기존코드 :

useEffect(() => { const loginStatus = getKakaoLoginStatus(); if (loginStatus === 'success') { const authorizationCode = getKakaoAuthorizationCode(); if (authorizationCode) { const handleLogin = async () => { try { setStatus('loading'); setMessage('로그인 처리 중...'); const result = await loginWithCode(authorizationCode); window.history.replaceState({}, '', '/kakao/callback'); setStatus('success'); setMessage('로그인이 완료되었습니다!'); if (result.accessToken) { setAccessToken(result.accessToken, 7); setRefreshToken(result.refreshToken, 30); localStorage.setItem('userId', result.userId); } timeout.current = setTimeout(() => { navigate('/home'); }, 3000); } catch (error) { console.error('❌ 백엔드 로그인 실패:', error); setStatus('error'); setMessage('로그인에 실패했습니다. 다시 시도해주세요.'); timeout.current = setTimeout(() => { navigate('/login'); }, 3000); } }; handleLogin(); } } else if (loginStatus === 'error') { const errorMessage = getKakaoErrorMessage(); setStatus('error'); setMessage(`로그인 실패: ${errorMessage || '알 수 없는 오류'}`); timeout.current = setTimeout(() => { navigate('/login'); }, 3000); } else { setMessage('카카오 로그인을 처리하고 있습니다...'); } return () => { if (timeout.current) clearTimeout(timeout.current); }; }, []);

현재 useEffect 내부의 하는 일들을 주르륵 정리해보면 다음과 같습니다

1. 3가지 상태 (success, error, pending) 초기 설정
2. 성공 케이스
1. authorization code 추출
2. 백엔드 로그인 요청 (handleLogin) 선언 및 바로 실행
3. 토큰 저장
4. /home 으로 이동
3. 실패 케이스
1. 에러메세지 설정 및 출력
2. /login 으로 이동
4. 에러 케이스 (앞선 실패케이스가 아닌, loginStatus==error인 상황)
1. url에서 에러메세지 추출 및 출력
2. /login으로 이동
5. 대기 케이스
1. 대기중 메세지 출력
6. 클린업
1. 컴포넌트 언마운트 시 setTime 타이머 클린

물론 모든 흐름이, useEffect 안에 사용되며 의존성배열을 빈 배열로 두어 한번만 실행이 되도록 하는게 맞다고 생각은 합니다.

하지만 멘토님의 피드백 대로, 굳이 useEffect안에 있으면서 가독성을 해치게 둘 필요가 없는 내용들이 보입니다.

멘토님 피드백 1: handleLogin()을 useEffect 밖으로 분리해보는 것은 어떨까요?

가장 먼저 보이는 내용입니다. 지금은 앞선 과정의 2-b에서, 굳이 선언과 사용을 동시에 useEffect안에서 하고 있습니다.

이는 가독성, 재사용성, 관심사 분리 등에 단점이 있을 것 같습니다,

단순히 handleLogin() 선언을 useEffect 밖으로 분리하며 해결하였습니다.

분리 결과:

const handleLogin = async (authorizationCode: string) => { try { setStatus('loading'); setMessage('로그인 처리 중...'); const result = await loginWithCode(authorizationCode); window.history.replaceState({}, '', '/kakao/callback'); setStatus('success'); setMessage('로그인이 완료되었습니다!'); if (result.accessToken) { setAccessToken(result.accessToken, 7); setRefreshToken(result.refreshToken, 30); localStorage.setItem('userId', result.userId); } timeout.current = setTimeout(() => { navigate('/home'); }, 3000); } catch (error) { console.error('❌ 백엔드 로그인 실패:', error); setStatus('error'); setMessage('로그인에 실패했습니다. 다시 시도해주세요.'); timeout.current = setTimeout(() => { navigate('/login'); }, 3000); } }; useEffect(() => { const loginStatus = getKakaoLoginStatus(); if (loginStatus === 'success') { const authorizationCode = getKakaoAuthorizationCode(); if (authorizationCode) { handleLogin(authorizationCode); } } else if (loginStatus === 'error') { const errorMessage = getKakaoErrorMessage(); setStatus('error'); setMessage(`로그인 실패: ${errorMessage || '알 수 없는 오류'}`); timeout.current = setTimeout(() => { navigate('/login'); }, 3000); } else { setMessage('카카오 로그인을 처리하고 있습니다...'); } return () => { if (timeout.current) clearTimeout(timeout.current); }; }, []);

멘토님 피드백 2 : 에러 처리하는 함수도 useEffect 밖으로 분리하면 useEffect가 더 간결해질 것 같습니다.

에러 처리 함수는 현재 에러메세지 가져오고, setStatus, setMessage, 화면 이동을 같이 하고 있습니다.

또 이런 문제도 있는 것 같습니다. 지금 handleLogin()의 에러 상황과 loginStatus==error인 상황에서의 에러처리 과정이 굉장히 유사하기에, 이 또한 중복 제거 처리를 진행할 수 있을 것 같습니다.

다른 팀원들이 이 상황을 쉽게 이해하게 하기 위해 약간의 상황 설명을 하자면,

loginStatus==error(useEffect안의 에러) 상황은, 카카오에서 리다이렉트를 할 때 에러가 발생한 상황입니다.

에러 이유로는 여러가지가 있을 것 같은데, 사용자가 카카오 로그인을 취소하거나 잘못된 클라이언트 id, 리다이렉트 url 등이 있을 것이고 카카오 서버 오류나 권환 문제 그런것들이 있을 것 같습니다.

handleLogin에서 에러처리를 하는 것은, 카카오에서 성공적으로 authorization code를 받은 후, 백엔드로 POST /api/login 요청을 보낼 때 발생한 에러입니다. 에러 원인으로는 백엔드 서버, 네트워크 연결, 만료되거나 잘못된 authorization code, 백엔드 로직 오류, 등이 있을 것 같습니다.

다른 상황이지만 현재 우리는 캐치한 에러에 대해 유사한 과정으로 status를 error로 바꾸고, console.error처리로 보여주고, login화면으로 다시 보내는 유사한 로직을 하고 있기에, 에러 처리 함수를 따로 뺄 수 있습니다,

const handleError = (errorMessage: string, shouldLog = false, error?: unknown) => { if (shouldLog && error) { console.error('❌ 로그인 실패:', error); } setStatus('error'); setMessage(errorMessage); timeout.current = setTimeout(() => { navigate('/login'); }, 3000); };

왜 굳이 shouldLog와 error인자가 있게 하였나? 라는 질문에는, 다음과 같이 답변할 수 있을 것 같습니다.

먼저 두가지 에러상황(카카오에러vs 백엔드 에러)의 차이가 있습니다.

카카오 에러에서는 사용자가 의도적으로 취소한 경우가 많고, url에서 이미 에러 정보를 확인할 수 있습니다. 사실상 콘솔 로깅이 딱히 필요하지 않다고 생각했습니다.

다만 백엔드 에러는 의도치 않게 발생한 오류인 것이고, 문제를 파악하기 위해 콘솔 로깅이 필요합니다.

이렇게 handleError를 선언하고 이후 코드를 이렇게 완성하였습니다.

const handleError = (errorMessage: string, shouldLog = false, error?: unknown) => { if (shouldLog && error) { console.error('❌ 로그인 실패:', error); } setStatus('error'); setMessage(errorMessage); timeout.current = setTimeout(() => { navigate('/login'); }, 3000); }; const handleLogin = async (authorizationCode: string) => { try { setStatus('loading'); setMessage('로그인 처리 중...'); const result = await loginWithCode(authorizationCode); window.history.replaceState({}, '', '/kakao/callback'); setStatus('success'); setMessage('로그인이 완료되었습니다!'); if (result.accessToken) { setAccessToken(result.accessToken, 7); setRefreshToken(result.refreshToken, 30); localStorage.setItem('userId', result.userId); } timeout.current = setTimeout(() => { navigate('/home'); }, 3000); } catch (error) { handleError('로그인에 실패했습니다. 다시 시도해주세요.', true, error); } }; useEffect(() => { const loginStatus = getKakaoLoginStatus(); if (loginStatus === 'success') { const authorizationCode = getKakaoAuthorizationCode(); if (authorizationCode) { handleLogin(authorizationCode); } } else if (loginStatus === 'error') { const errorMessage = getKakaoErrorMessage(); handleError(`로그인 실패: ${errorMessage || '알 수 없는 오류'}`); } else { setMessage('카카오 로그인을 처리하고 있습니다...'); } return () => { if (timeout.current) clearTimeout(timeout.current); }; }, []);

이렇게 되면, 카카오 에러 시에는 콘솔에 찍히는게 없습니다. 백엔드 에러시에는 콘솔에 에러 내용이 보이게 됩니다.

지금 실행으르 해보면, 로그인이 정상적으로 되는 듯 하다가 콘솔창에 이런 메세지가 뜹니다.

KakaoCallbackPage.tsx:21

❌ 로그인 실패:

  1. AxiosError {message: 'timeout of 10000ms exceeded', name: 'AxiosError', code: 'ECONNABORTED', config: {…}, request: XMLHttpRequest, …}
handleError@KakaoCallbackPage.tsx:21
handleLogin@KakaoCallbackPage.tsx:52
Promise.then
mutationFn@useMutationApi.ts:18

에러가 출력이 되었다는 것은? 백엔드 에러이고, 당연히 지금 백엔드와 연결이 안되어있기에 이러한 에러 메세지를 알 수 있게 출력이 되는 상황입니다.

간단히 설명하자면, authorization code를 올바르게 받아오고, handleLogin과정에서 await loginwithcode를 진행하지만, 백엔드와 연결이 되어있지 않으니 오류가 콘솔에 찍히고, 화면에는 “로그인에 실패했습니다. …” 메세지가 나타납니다.

멘토님 피드백 3: kakaoLoginButton.tsx 와 confirmButton.tsx의 파일명을 PascalCase로 수정해주는 것이 좋겠습니다.

파일명, 함수명을 만들 때 자꾸 생각없이 마음대로 짓는 경우가 많은것 같습니다. 해당 피드백은 지난 리뷰때도 있었고, 이번에도 놓친 부분이기에 또 재발하지 않게 유의하도록 하겠습니다. 감사합니다.

파일명 변경과 함께, 각 파일을 참조하고 있는 파일들의 경로도 변경을 완료했으나, 한가지 문제가 생겼습니다. vercel ci cd를 통과하지 못하는 문제가 발생했습니다. 로컬 빌드에서는 정상적으로 빌드가 완료되었으나, vercel에서 갑자기 빌드 오류가 생기는 것에 의문이 있었습니다.

서칭 결과, 운영체제별 파일 시스템의 차이도 있고, (제가 쓰고 있는 macos는 대소문자 구분 안하는 문제), git의 파일명 추적 방식이 git은 기본적으로 대소문자 변경을 감지하지 않고 파일명만 바뀌고 내용이 같으면 변경이 없는것으로 인지합니다. 이때, git mv 명령어를 사용해야만 파일면 변경을 추적할 수 있습니다.

# 파일명 대소문자 변경 git mv oldFileName.tsx NewFileName.tsx # 커밋 및 푸시 git commit -m "fix: 파일명 대소문자 수정" git push origin branch-name

와 같은 방식으로 진행, 즉 mv를 사용해 대소문자 변경을 추적할 수 있게 하고 커밋 및 푸시를 진행하면 정상적으로 빌드가 됨을 알 수 있었습니다.