inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

[리뉴얼] 맛집 지도앱 만들기 (React Native & NestJS)

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

해결된 질문

335

김용민

작성한 질문수 74

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()나 이런 것들을 활용했지만, 제대로 로그아웃이 동작하지 않아. 어떻게 해결할 수 있을지 의견을 구하고자 질문을 남깁니다.

react-native typescript nestjs react-query zustand

답변 2

0

Kyo

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

0

김용민

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

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

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

react-native-screens 버전 호환 문제

2

153

1

안드로이드 실행 중 Drawer네비게이션과 MapView 성능 문제

0

103

2

해당 강의 부분은 실제 활용하기에 부족해 제가 해결한 방법입니다.

0

96

1

소스코드가 강의 순서랑 다른가요?

0

72

2

현재 Windows에서 VsCode로 작업 중인데 추후에 IOS도 가능하게 하려면

0

115

2

react-native-fast-image는 react 19 버전에서 설치가 안되나요?

0

209

2

SQL Shell의 역할이 무엇인가요?

0

91

1

혹시 해당 강의에서invalidateQueries를 사용한 이유가 있을까요?

0

79

2

빠르게 실행해보고싶습니다.

0

83

1

강의 수강 순서 관련

0

71

1

애뮬레이터 실행 방법

0

99

2

무료 Apple ID로 실기기 테스트 가능한가요?

0

90

2

ios 실기기 연결

0

70

2

npm run ios에러

0

74

1

10월 삭제 예정인 강의는 이유를 알 수 있을까요?

0

101

1

캘린더 개발 후에 navigation 에 대해서 궁금한 점이 있습니다.

0

51

1

안드로이드 위치 권한 이슈 2가지 문의

0

71

1

지도가 보이려면 음.. 작성해주신 스타일과 다르게

0

74

0

제대로 설치한거같은데 안드로이드랑 ios 둘다 앱실행이 안되는것같아요

0

83

3

강의 내용을 보면 전체적으로 function 함수 키워드를 사용하시는데

0

69

2

강의 3-9 에서 useGetRefreshToken 훅 안에 즉시 함수로 처리하는 이유가 궁금합니다!

0

82

2

사내에서 figma.com 업로드 안되나요?

0

96

1

AWS EC2 + RDS 설정

0

85

2

안드로이드 안켜집니다.

0

91

2