인프런 커뮤니티 질문&답변

김용민님의 프로필 이미지

작성한 질문수

맛집 지도앱 만들기 (React Native + NestJS)

[8-4] 설정 스크린 추가 / 로그아웃 문제 해결

로그아웃 관련해서 질문 있습니다.

해결된 질문

24.08.01 17:28 작성

·

109

·

수정됨

0

import {useEffect} from 'react';
import {useMutation, useQuery} from '@tanstack/react-query';
import {queryClient} from 'containers/TanstackQueryContainer.tsx';

import {getAccessToken, logout, postLogin, postSignup, socialLogin} from 'apis';
import {UseMutationCustomOptions} from 'types/mutations/common.ts';
import {
  numbers,
  removeEncryptStorage,
  removeHeader,
  setEncryptStorage,
  setHeader,
} from 'utils';
import {queryKeys, storageKeys} from 'constants/storageKeys/keys.ts';

function useSignup(mutationOptions?: UseMutationCustomOptions) {
  return useMutation({
    mutationFn: postSignup,
    throwOnError: error => Number(error.response?.status) >= 500,
    ...mutationOptions,
  });
}

function useLogin(mutationOptions?: UseMutationCustomOptions) {
  return useMutation({
    mutationFn: postLogin,
    onSuccess: data => {
      // 토큰 저장.
      const accessToken = data.result.accessToken;
      const refreshToken = data.result.refreshToken;

      setEncryptStorage(storageKeys.ACCESS_TOKEN, accessToken);
      setEncryptStorage(storageKeys.REFRESH_TOKEN, refreshToken);

      setHeader('Authorization', accessToken);
    },
    onSettled: () => {
      queryClient.refetchQueries({
        queryKey: [queryKeys.AUTH, queryKeys.GET_ACCESS_TOKEN],
      });
    },
    throwOnError: error => Number(error.response?.status) >= 500,
    ...mutationOptions,
  });
}

function useSocialIdTokenLogin(mutationOptions?: UseMutationCustomOptions) {
  return useMutation({
    mutationFn: socialLogin,
    onSuccess: ({result}) => {
      setHeader('Authorization', result.accessToken);
      setEncryptStorage(storageKeys.REFRESH_TOKEN, result.refreshToken);
    },
    onSettled: () => {
      queryClient.refetchQueries({
        queryKey: [queryKeys.AUTH, queryKeys.GET_ACCESS_TOKEN],
      });
    },
    throwOnError: error => Number(error.response?.status) >= 500,
    ...mutationOptions,
  });
}

function useGetRefreshToken() {
  const {data, error, isSuccess, isError, isPending} = useQuery({
    queryKey: [queryKeys.AUTH, queryKeys.GET_ACCESS_TOKEN],
    queryFn: getAccessToken,
    staleTime: numbers.ACCESS_TOKEN_REFRESH_TIME,
    refetchInterval: numbers.ACCESS_TOKEN_REFRESH_TIME,
    refetchOnReconnect: true,
    refetchIntervalInBackground: true,
  });

  useEffect(() => {
    if (isSuccess) {
      setHeader('Authorization', `Bearer ${data?.result.accessToken}`);
      setEncryptStorage(storageKeys.ACCESS_TOKEN, data.result.accessToken);
      setEncryptStorage(storageKeys.REFRESH_TOKEN, data.result.refreshToken);
      console.log(isSuccess, '성공');
    }
  }, [isSuccess]);

  useEffect(() => {
    if (isError) {
      removeHeader('Authorization');
      removeEncryptStorage(storageKeys.REFRESH_TOKEN);
    }
  }, [isError]);

  return {isSuccess, isError, error, data, isPending};
}

function useLogout(mutationOptions?: UseMutationCustomOptions) {
  return useMutation({
    mutationFn: logout,
    onSuccess: () => {
      removeHeader('Authorization');
      removeEncryptStorage(storageKeys.REFRESH_TOKEN);
      queryClient.resetQueries({queryKey: [queryKeys.AUTH, 'getAccessToken']});
      queryClient.clear();
    },
    throwOnError: error => Number(error.response?.status) >= 500,
    ...mutationOptions,
  });
}

function useAuth() {
  const signUpMutation = useSignup();
  const loginMutation = useLogin();
  const socialIdTokenMutation = useSocialIdTokenLogin();
  const getNewAccessToken = useGetRefreshToken();
  const logoutMutation = useLogout();
  const isLogin = getNewAccessToken.isSuccess;
  const isLoginLoading = getNewAccessToken.isPending;

  return {
    signUpMutation,
    loginMutation,
    socialIdTokenMutation,
    isLogin,
    logoutMutation,
    isLoginLoading,
    getNewAccessToken,
  };
}

export default useAuth;
// RootNavigator.tsx

import FeedTabNavigator from '../tab/FeedTabNavigator.tsx';
import AuthStackNavigator from '../stack/AuthStackNavigator.tsx';
import {useEffect} from 'react';

import SplashScreen from 'react-native-splash-screen';
import useAuth from '../../hooks/queries/AuthScreen/useAuth.ts';

export default function RootNavigator() {
  const {isLogin, getNewAccessToken} = useAuth();
  console.log(isLogin);

  useEffect(() => {
    setTimeout(() => {
      SplashScreen.hide();
    }, 1000);
  }, []);

  return <>{isLogin ? <FeedTabNavigator /> : <AuthStackNavigator />}</>;
}

안녕하세요, 강사님, 강사님 강의를 전체 모두 듣고, 비슷한 느낌으로 풀스택으로 앱개발을 진행하고있습니다.

강사님 강의처럼 쿼리를 활용하여 로그인 기능을 구현하고 싶어 useLogout훅에 onSettled를 넣었을 때는 강사님처럼 동일한 이상증상이 발생했고, 이를 해결하기 위해 강사님께서는
onSuccess부분에
queryClient.resetQueries({queryKey: [queryKeys.AUTH]});
이 부분을 넣어주셔서 해결을하셨는데, 저는 해당 부분을 넣어도, 로그아웃이 되었다가, 바로 refreshToken으로 accessToken을 재발급받아, 로그인이 되지 않는 현상이 일어나고있습니다. clear()나 이런 것들을 활용했지만, 제대로 로그아웃이 동작하지 않아. 어떻게 해결할 수 있을지 의견을 구하고자 질문을 남깁니다.

답변 2

0

Kyo님의 프로필 이미지
Kyo
지식공유자

2024. 08. 01. 22:23

저는 socialLogin,logout등의 api를 어떻게 구현하셨는지 모르고, useLogin이나 isLogin체크방식도 저와 다르고, 로그아웃 onSuccess 함수나 쿼리키도 다르기 때문에 정확히 어떤 부분때문인지 알기가 어렵습니다. 강의 코드로 진행해보시는게 좋을 것 같습니다.

0

김용민님의 프로필 이미지
김용민
질문자

2024. 08. 01. 18:56

해결 방법을 찾았습니다. 근데 왜인지 모르겠어서 알고 싶습니다.

기존 코드는 아래와 같습니다.

const logout = async (): Promise<TLogout> => {
  const {data} = await axiosInstance.get('/api/v1/auth/logout');

  return data;
};
const logout = async (): Promise<TLogout> => {
  const {data} = await axiosInstance.get('/api/v1/auth/logout');
  await removeEncryptStorage(storageKeys.REFRESH_TOKEN);

  return data;
};

d이렇게 logout api에서, 스토리지에 refreshToken키를 제거하는 함수를 추가했더니 동작을 합니다.
useAuth query 코드중 logout부분에서 분명히, 스토리지를 제거하는 코드와 헤더를 제거하는 코드를 작성했는데 거기서는 동작하지 않지만.

logout api에 중복적으로 스토리지를 제거하는 코드를 작성했을 떄 왜 정상적으로 동작하는지 이해할 수 없어 알고 싶어 질문 남깁니다.