React 부트캠프 TIL 24

정다롱·2024년 9월 11일

내일배움캠프 TIL

목록 보기
23/39

💥🥹 MBTI 페이지 트러블 슈팅


1. context 프로바이더 적용 오류

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("/"); // 로그인이 완료되면 홈으로 이동시켜주기

바랐던 실행 구조는 이랬다

  1. 로그인 정보를 입력하고 버튼을 누르면 handleLogin 실행
  2. await login 실행 후 완료가 되면 로그인 상태가 true로 변경
  3. 메인 페이지로 이동
  4. 로그인 과정에서 에러 발생시 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)}

실수로 중괄호를 한번만 쓰고 저장을 눌렀는데 프리티어가 엥? 괄호 없는데요? 하며 소괄호를 추가해준 것에서 시작된 오류였다. 소괄호를 중괄호로 바꿔주니 거짓말처럼 한번에 작동이 되는 걸 확인할 수 있었다.

이렇게 사소한 걸 못보고 지나쳐서 왜 안될까 한참을 고민했다니. 혹시라도 컨텍스트를 쓰다가 제대로 안 되거나 오류가 난다면 이 부분을 꼭 한번쯤 확인해보길 바란다.



2. useQuery에서 사라진 onSuccess, onError

  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;
   }

원했던 실행 구조

  1. 전역으로 사용하는 context에서 로그인한 유저 정보를 가져오기 위해 getUserProfile을 호출하여 loginUser 데이터를 받아온다
  2. 로컬 스토리지에 토큰이 저장되어 있을 때만 실행한다
  3. 만약 해당 토큰이 만료된 토큰이라면 getUserProfile 내부에서 오류를 내려준다.
  4. getUserProfile에서 오류가 발생했고 해당 오류의 응답이 401이라면 세션 만료라는 안내를 띄우고 로그아웃 처리(로컬 스토리지에서 토큰 삭제)를 하고 로그인 페이지로 이동시킨다.

그런데 아무리 해봐도 onError부분이 실행되지 않았다. 대체 왜 안되는 걸까 30분동안 throw new Error, throw Error, try catch 지우고 에러만 반환하기 등 해보았지만 해결되는 건 없었다.

토큰이 만료된 경우만 계속해서 테스트 하다가 일단 다른 기능을 먼저 구현해야겠다 싶어 다시 토큰 유효 시간을 늘리고 로그인을 했는데 그 과정에서 onSuccess 부분도 실행이 되지 않고 있는 걸 알았다.

사실은 없어졌답니다!

그렇다. onSuccess, onError는 useQuery에서 사라진 것들이었다.

검색했을 때 나왔던 문서, tanstack Query 4

현재 사용하는 버전은...
"@tanstack/react-query": "^5.55.4",

최신 버전인 5버전의 공식 문서

그냥 내가 없는 명령어를 계속 사용해서 발생한 문제였다. onError같은 건 없으니까 error를 잡지 못하고 안내창을 띄우지도 않는게 당연한 거였다.

블로그도 참고하고 ai도 사용했었는데.. 아마 그게 모두 예전 버전을 참고하고 있어서 전혀 모르고 있었던 거 아닐까. 사실 검색도 대충 해보긴 했다...🥹 지나가던 튜터님 붙잡고 여쭤보니 친절하게 최신 버전 공식 문서를 보여주셨다. 작년에 삭제되었다고...

그럼 onError와 같은 처리를 어떻게 하는가?

참고 블로그 1
참고 블로그 2

커스텀을 해서 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의 값이 바뀔 때마다 조건문을 실행하도록 했다.

  1. 토큰이 없다면 로그인 상태를 false로 만들고 종료
  2. 토큰이 있는데 에러가 발생했을 경우 - 안내를 띄우고 로그아웃 처리, 로그인 페이지로 이동 후 종료
  3. 토큰이 있고 에러도 발생하지 않았을 경우 - 로그인 상태를 true로 변경하고 종료

이렇게 하니 원하던 대로 동작하기 시작했다!

설마 왜 안되나 계속 붙잡고 있던게 사라진 기능일 줄은 꿈에도 몰랐다. 이래서 튜터님들이 공식 문서를 항상 꼼꼼히 보라고 하셨구나... 그런데 봤는데도 버전 확인을 안해서 이런 고생을 하다니 스스로가 너무 황당했다.

0개의 댓글