Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/app/_components/AuthInitializer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useEffect } from 'react';

import { useUser } from '@/domain/Auth/hooks/useUser';
import { useRoamReadyStore } from '@/shared/store';

/**
* @component AuthInitializer
Expand All @@ -13,11 +14,15 @@ import { useUser } from '@/domain/Auth/hooks/useUser';
*/
export default function AuthInitializer() {
const { refetch } = useUser(); // 쿼리 객체를 받아서
const user = useRoamReadyStore((state) => state.user);

useEffect(() => {
// 앱 로드시 강제로 한번 요청 실행
refetch();
}, [refetch]);
// 스토어에 사용자 정보가 있을 경우 (즉, 이전에 로그인했을 경우)에만
// 세션 유효성 검사를 위해 refetch를 실행합니다.
if (user) {
refetch();
}
}, [refetch, user]);
Comment on lines +20 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

user 의존 refetch는 중복 호출·루프 위험 → 제거 권장.

  • useUser 내부에서 enabled: !!user로 이미 자동 fetch가 발생합니다.
  • useUser 성공 시 setUser가 호출되면 user 레퍼런스가 바뀌고, 본 useEffect가 다시 refetch를 불러 무한/과도한 요청으로 이어질 수 있습니다(중복 fetch 또는 fetch-→setUser→refetch 반복).

불필요한 refetch와 store 의존을 제거해 Initializer를 “훅 마운트만” 하도록 단순화하세요.

-'use client';
-
-import { useEffect } from 'react';
+'use client';
 
 import { useUser } from '@/domain/Auth/hooks/useUser';
-import { useRoamReadyStore } from '@/shared/store';
 
 export default function AuthInitializer() {
-  const { refetch } = useUser(); // 쿼리 객체를 받아서
-  const user = useRoamReadyStore((state) => state.user);
-
-  useEffect(() => {
-    // 스토어에 사용자 정보가 있을 경우 (즉, 이전에 로그인했을 경우)에만
-    // 세션 유효성 검사를 위해 refetch를 실행합니다.
-    if (user) {
-      refetch();
-    }
-  }, [refetch, user]);
+  // enabled: !!user 로 이미 내부에서 자동 실행됨
+  useUser();
   return null;
 }

대안(정 꼭 필요하다면): status/isFetching을 함께 받아 “idle이고 user가 생겼을 때 1회만” refetch 하도록 강하게 가드하세요.

const { refetch, status, isFetching } = useUser();
useEffect(() => {
  if (user && status === 'idle' && !isFetching) refetch();
}, [refetch, user, status, isFetching]);

Also applies to: 17-17, 6-6, 3-3

🤖 Prompt for AI Agents
In src/app/_components/AuthInitializer.tsx around lines 20 to 25, the useEffect
that calls refetch when user exists causes redundant/looping fetches because
useUser already auto-fetches with enabled: !!user and setUser changes user
reference; remove the user-dependent refetch to make the initializer only mount
the hook (delete the if (user) refetch() call and remove user from the effect
deps so effect runs only on mount/refetch identity), or if you must keep a
guarded refetch implement a one-time guard by also reading status/isFetching
from useUser and only calling refetch when user exists AND status === 'idle' AND
!isFetching so it runs once.


return null;
}
35 changes: 9 additions & 26 deletions src/domain/Auth/hooks/useUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ import { useRoamReadyStore } from '@/shared/store';
* @description
* 현재 로그인된 사용자의 정보를 가져와 클라이언트의 전역 상태(Zustand)와 동기화하는 커스텀 훅입니다.
*
* 이 훅은 Tanstack Query의 `useQuery`를 사용하여 `getMe` 서비스 함수를 호출하고,
* `useEffect`를 통해 쿼리의 결과(성공 또는 실패)에 따라 전역 상태를 업데이트합니다.
*
* @feature
* - Zustand 스토어에 사용자 정보가 존재할 때만(`enabled: !!userInStore`) API를 호출하여 세션을 검증합니다.
* - 이를 통해 로그아웃 상태의 사용자가 불필요한 API를 호출하여 401 에러가 발생하는 것을 방지합니다.
* - `useEffect`를 통해 쿼리의 결과(성공 또는 실패)에 따라 전역 상태를 업데이트합니다.
* - **조회 성공 시 (`isSuccess`)**: `setUser` 액션을 호출하여 전역 스토어에 최신 사용자 정보를 저장합니다.
* - **조회 실패 시 (`isError`)**: `clearUser` 액션을 호출하여 전역 스토어와 localStorage의 사용자 정보를 제거합니다.
*
* 이 방식은 앱이 시작될 때(`AuthInitializer` 내부에서 호출) 실제 서버의 인증 상태와 UI 상태의 불일치를 방지하는 핵심적인 역할을 수행합니다.
*
* @returns {object} Tanstack Query의 `useQueryResult` 객체. 포함하는 주요 속성은 다음과 같습니다:
* - `data`: 성공 시 가져온 사용자 정보 (`User` 타입).
* - `isLoading`: 데이터 로딩 중인지 여부를 나타내는 boolean 값.
Expand All @@ -25,37 +24,21 @@ import { useRoamReadyStore } from '@/shared/store';
*
* @property {Array<string>} queryKey - Tanstack Query가 이 데이터를 식별하고 캐싱하는 고유한 키 (`['user', 'me']`).
* @property {Function} queryFn - 실제 데이터를 가져오는 비동기 함수 (`getMe`).
* @property {boolean} enabled - 스토어에 사용자 정보가 있을 때만 쿼리를 활성화하는 조건부 플래그.
* @property {number} retry - 쿼리 실패 시 재시도 횟수 (1회).
* @property {number} staleTime - 데이터를 'fresh' 상태로 유지하는 시간 (5분).
*
* @example
* ```typescript
* import { useUser } from '@/domain/Auth/hooks/useUser';
*
* function UserProfile() {
* const { data: user, isLoading, isError } = useUser();
*
* if (isLoading) {
* return <span>로딩 중...</span>;
* }
*
* if (isError) {
* 이 컴포넌트가 렌더링될 시점에는 AuthInitializer가 이미
* 로그아웃 처리를 완료했으므로, 보통 이 분기는 보이지 않습니다.
* return <span>로그인이 필요합니다.</span>;
* }
* @property {boolean} refetchOnWindowFocus - 창이 다시 포커스될 때 쿼리를 다시 가져올지 여부.
* @property {boolean} refetchOnReconnect - 네트워크가 다시 연결될 때 쿼리를 다시 가져올지 여부.
*
* return <h1>안녕하세요, {user.nickname}님!</h1>;
* }
* ```
*/
export const useUser = () => {
const setUser = useRoamReadyStore((state) => state.setUser);
const clearUser = useRoamReadyStore((state) => state.clearUser);
const user = useRoamReadyStore((state) => state.user);

const queryResult = useQuery({
queryKey: ['user', 'me'],
queryFn: getMe,
enabled: !!user,
retry: 1,
refetchOnWindowFocus: true,
refetchOnReconnect: true,
Expand Down