[아웃소싱] QueryClientProvider 적용과 인증 상태 구현하기

y1nlog·2025년 2월 28일
0

초안

  • useAuthListener.js
    인증 상태 변경에 따른 전역상태를 변경시키기 위해 supabase의 onAuthStateChange 메소드를 초기 렌더링 시에 한 번만 실행시키면서, userInfo state를 업데이트 하는 로직을 구성.
import { useEffect } from 'react';
import { supabase } from '../api/supabaseClient';
import useAuthStore from '../../stores/useAuthstore';
import useGetUserInfo from './useGetUserInfo';

const useAuthListener = () => {
  const setUserInfo = useAuthStore().setUserInfo;
  const { data: userData } = useGetUserInfo();

  useEffect(() => {
    // 인증 상태 변경 감지 및 자동 업데이트
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange(async (_, session) => {
      const userInfo = {
        id: session?.user.id,
        email: session?.user.email,
        nickname: userData?.nickname || '',
        profile_img_url: userData?.profile_img_url || '',
      };

      setUserInfo(userInfo);
    });
    return () => {
      subscription.unsubscribe(); // Cleanup when unmounting
    };
  }, []);
};

export default useAuthListener;
  • useGetUserInfo.js 커스텀 훅
    useQuery를 통해 탠스택쿼리로 BaaS 서버에서 데이터를 가져오는 로직
import { useQuery } from '@tanstack/react-query';
import getUserInfo from '../api/getUserInfo';
import useAuthStore from '../../stores/useAuthstore';

const useGetUserInfo = () => {
  const userId = useAuthStore((state) => state.userInfo.id);

  return useQuery({
    queryKey: ['userInfo', userId],
    queryFn: () => getUserInfo(userId),
    enabled: !!userId,
  });
};

export default useGetUserInfo;
  • getUserInfo.js
    코드 가독성 향상을 위해, useQuery에 사용될 queryFn을 분리하여 내보내는 구조로 작성하였다.
import { supabase } from './supabaseClient';

const getUserInfo = async (authId) => {
  let { data, error } = await supabase
    .from('users')
    .select('nickname, profile_img_url')
    .eq('id', authId);

  if (error) {
    throw new Error(error);
  }

  return data;
};

export default getUserInfo;

문제 발생

에러메세지

@tanstack_react-query.js?v=6ec93a7b:2839
Uncaught Error: No QueryClient set, use QueryClientProvider to set one
at useQueryClient (@tanstack_react-query.js?v=6ec93a7b:2839:11)
at useBaseQuery (@tanstack_react-query.js?v=6ec93a7b:3040:18)
at useQuery (@tanstack_react-query.js?v=6ec93a7b:3111:10)
at useGetUserInfo (useGetUserInfo.js:21:10)
at useAuthListener (useAuthListener.js:8:30)
at App (App.jsx:9:3)

QueryClient가 세팅되어 있지 않으니,
QueryClientProvider를 사용하라는 에러가 발생했다.

문제 원인

App.jsx 내에서 useAuthListener를 실행하는 위치가 잘못되었다.
queryClientProvider가 감싸고 있는 영역 안으로 넣어주어야 한다.

그래서 기존의 useEffect 코드를 컴포넌트화하여 queryClientProvider 내부에 위치시키는 것으로 파훼!

해결 코드

  • App.jsx 수정
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import Router from './routes/Router';
import AuthListener from './components/auth/AuthListener';

const App = () => {
  const queryClient = new QueryClient();

  return (
    <>
      <QueryClientProvider client={queryClient}>
        <AuthListener />;
        <Router />
      </QueryClientProvider>
    </>
  );
};

export default App;
  • AuthListener.jsx 컴포넌트 생성
import React, { useEffect } from 'react';
import { supabase } from '../../libs/api/supabaseClient';
import useAuthStore from '../../stores/useAuthstore';
import useGetUserInfo from '../../libs/hooks/useGetUserInfo';

const AuthListener = () => {
  const setUserInfo = useAuthStore((state) => state.setUserInfo);
  const { data: userData, isPending } = useGetUserInfo();

  useEffect(() => {
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange(async (_, session) => {
      const userInfo = {
        id: session?.user.id,
        email: session?.user.email,
        nickname: userData?.nickname || '',
        profile_img_url: userData?.profile_img_url || '',
      };

      setUserInfo(userInfo);
    });

    // cleanUp
    return () => {
      subscription.unsubscribe();
    };
  }, [userData]); // userData 변경 시 리렌더링

  if (isPending) return <>기다려.</>;

  return <></>;
};

export default AuthListener;
  • 결과

향후 개선 방향(?)

실마리를 남겨 보자.

  • useEffect를 하나 더 추가해서, 처음에 onAuthStateChange로 불러오는 로직은 그냥 수행되도록 두고, userData 변동 시 setUserInfo로 유저 정보를 업데이트 하는 로직을 시도해 보았다.
  useEffect(() => {
    const {
      data: { subscription },
    } = supabase.auth.onAuthStateChange(async (_, session) => {
      const userInfo = {
        id: session?.user.id,
        email: session?.user.email,
        nickname: userData?.nickname || '',
        profile_img_url: userData?.profile_img_url || '',
      };

      setUserInfo(userInfo);
    });
    return () => {
      subscription.unsubscribe();
    };
  }, []); // userData 의존성 배열 삭제

  useEffect(() => {
    if (!userData) {
      return;
    }
    setUserInfo((prev) => ({
      ...prev,
      nickname: userData.nickname,
      profile_img_url: userData.profile_img_url,
    }));
    console.log('final ===>');
  }, [userData]);
  • 결과

    처음에는 출력이 되지 않지만

파일 내에서 저장을 한 번 누르면 닉네임 값이 들어가는 걸 볼 수 있다.
다만 브라우저에서 새로고침을 하면 다시 원점 복귀.

일단 첫번째 방향으로 진행하면서 추후 개선해보는 방식으로 종결.

여전히 초기 렌더링 시에 여러번 리렌더링이 발생하는 성능 상의 결점이 있다.
일단 결과값은 잘 불러와지니까 리드타임 내 프로젝트 완수를 위해서 잠시 미뤄두기로 ...

profile
FrontEnd Developer

0개의 댓글