const handleLogin = async (e) => {
e.preventDefault();
try {
await login(userInfo);
setIsLogin(true);
navigate("/");
} catch (error) {
alert(error);
}
};
실행하고자 했던 함수는 이 부분이다.
// await login(userInfo); axios.post 로그인 처리하는 함수
export const login = async (userData) => {
try {
const response = await axios.post(
`${API_URL}/login?expiresIn=1h`,
userData
);
localStorage.setItem("accessToken", response.data.accessToken);
return response;
} catch (error) {
throw Error(error.response.data.message);
}
};
setIsLogin(true); // AuthContext에서 가져온 로그인 상태 변경을 위한 setState
navigate("/"); // 로그인이 완료되면 홈으로 이동시켜주기
바랐던 실행 구조는 이랬다
- 로그인 정보를 입력하고 버튼을 누르면 handleLogin 실행
- await login 실행 후 완료가 되면 로그인 상태가 true로 변경
- 메인 페이지로 이동
- 로그인 과정에서 에러 발생시 throw Error(error.response.data.message); 해당 메세지 출력하고 실행 종료
그런데 로그인을 실행하면
TypeError: setIsLogin is not a function // at handleLogin
라는 에러가 뜬다.
setIsLogin is not a function ..?
const Router = () => {
return (
<BrowserRouter>
<AuthProvider>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
<Route path="/profile" element={<Profile />} />
<Route path="/test" element={<TestPage />} />
<Route path="/results" element={<TestResultPage />} />
</Routes>
</Layout>
</AuthProvider>
</BrowserRouter>
);
};
라우터에서 잘 불러 왔고
const { setIsLogin, loginUser } = useContext(AuthCotext);
컴포넌트 안에서도 잘 불러왔다.
그런데 왜?
return (
<AuthCotext.Provider value={(loginUser, isLogin, setIsLogin)}>
{children}
</AuthCotext.Provider>
);
이상한 점을 바로 알아보시겠나요?
<AuthCotext.Provider value={(loginUser, isLogin, setIsLogin)}>
{(loginUser, isLogin, setIsLogin)}
실수로 중괄호를 한번만 쓰고 저장을 눌렀는데 프리티어가 엥? 괄호 없는데요? 하며 소괄호를 추가해준 것에서 시작된 오류였다. 소괄호를 중괄호로 바꿔주니 거짓말처럼 한번에 작동이 되는 걸 확인할 수 있었다.
이렇게 사소한 걸 못보고 지나쳐서 왜 안될까 한참을 고민했다니. 혹시라도 컨텍스트를 쓰다가 제대로 안 되거나 오류가 난다면 이 부분을 꼭 한번쯤 확인해보길 바란다.
const { data: loginUser, isError } = useQuery({
queryKey: ["userData"],
queryFn: () => getUserProfile(token),
enabled: !!token,
onSuccess: () => {
console.log("onSuccess");
setIsLogin(true);
},
onError: (error) => {
if (error.response && error.response.status === 401) {
alert("세션이 만료되었습니다. 재로그인해주세요.");
logout();
navigate("/login");
}
},
});
export const getUserProfile = async (token) => {
try {
const response = await axios.get(`${API_URL}/user`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
} catch (error) {
console.error("프로필 가져오기 실패:", error);
return error;
}
원했던 실행 구조
- 전역으로 사용하는 context에서 로그인한 유저 정보를 가져오기 위해 getUserProfile을 호출하여 loginUser 데이터를 받아온다
- 로컬 스토리지에 토큰이 저장되어 있을 때만 실행한다
- 만약 해당 토큰이 만료된 토큰이라면 getUserProfile 내부에서 오류를 내려준다.
- getUserProfile에서 오류가 발생했고 해당 오류의 응답이 401이라면 세션 만료라는 안내를 띄우고 로그아웃 처리(로컬 스토리지에서 토큰 삭제)를 하고 로그인 페이지로 이동시킨다.
그런데 아무리 해봐도 onError부분이 실행되지 않았다. 대체 왜 안되는 걸까 30분동안 throw new Error, throw Error, try catch 지우고 에러만 반환하기 등 해보았지만 해결되는 건 없었다.
토큰이 만료된 경우만 계속해서 테스트 하다가 일단 다른 기능을 먼저 구현해야겠다 싶어 다시 토큰 유효 시간을 늘리고 로그인을 했는데 그 과정에서 onSuccess 부분도 실행이 되지 않고 있는 걸 알았다.
그렇다. onSuccess, onError는 useQuery에서 사라진 것들이었다.
검색했을 때 나왔던 문서, tanstack Query 4

현재 사용하는 버전은...
"@tanstack/react-query": "^5.55.4",
그냥 내가 없는 명령어를 계속 사용해서 발생한 문제였다. onError같은 건 없으니까 error를 잡지 못하고 안내창을 띄우지도 않는게 당연한 거였다.
블로그도 참고하고 ai도 사용했었는데.. 아마 그게 모두 예전 버전을 참고하고 있어서 전혀 모르고 있었던 거 아닐까. 사실 검색도 대충 해보긴 했다...🥹 지나가던 튜터님 붙잡고 여쭤보니 친절하게 최신 버전 공식 문서를 보여주셨다. 작년에 삭제되었다고...
커스텀을 해서 onError 처리를 할 수 있다곤 하는데... 딱 봐도 뭔가 복잡해보이고 그래서 다른 방법을 생각해보다가 useQuery의 반환값 중 하나인 isError(오류 발생 유무를 불리언 타입으로 반환)를 사용하면 되지 않을까? 하는 생각이 들었다.
const { data: loginUser, isError } = useQuery({
queryKey: ["userData"],
queryFn: () => getUserProfile(token),
enabled: !!token,
});
useEffect(() => {
if (!token) {
setIsLogin(false);
return; // 토큰이 없으면 로그인 상태를 false로 설정
} else if (token) {
if (isError) {
alert("만료된 토큰입니다. 다시 로그인 해주세요!");
logout();
navigate("/login");
return;
}
setIsLogin(true);
return;
}
}, [token, isError]);
우선 로그인 유저의 데이터를 가져오는 부분은 똑같이 처리하고 isError를 반환받는다.
useEffect를 사용하여 token, isError의 값이 바뀔 때마다 조건문을 실행하도록 했다.
- 토큰이 없다면 로그인 상태를 false로 만들고 종료
- 토큰이 있는데 에러가 발생했을 경우 - 안내를 띄우고 로그아웃 처리, 로그인 페이지로 이동 후 종료
- 토큰이 있고 에러도 발생하지 않았을 경우 - 로그인 상태를 true로 변경하고 종료
이렇게 하니 원하던 대로 동작하기 시작했다!
설마 왜 안되나 계속 붙잡고 있던게 사라진 기능일 줄은 꿈에도 몰랐다. 이래서 튜터님들이 공식 문서를 항상 꼼꼼히 보라고 하셨구나... 그런데 봤는데도 버전 확인을 안해서 이런 고생을 하다니 스스로가 너무 황당했다.