본문으로 건너뛰기

상태 관리

Lumie는 하나의 전역 스토어를 쓰지 않고 상태 계층에 따라 서로 다른 도구를 사용합니다.

상태 계층

상태 유형주요 도구현재 사용 방식
서버 상태TanStack Query세션, 목록, 상세 뷰, 대부분의 CRUD 기반 화면
폼 상태React Hook Form로그인, 회원가입, 온보딩, 설정, CRUD 다이얼로그, 다단계 폼
공유 클라이언트 UI 상태Zustand공유 인증 모달 상태와 서버에서 주입한 공개 테넌트 컨텍스트
라우트 상태Next.js search params목록 필터, 정렬, 페이지네이션, 모달 진입 파라미터
로컬 일시 UI 상태React 컴포넌트 상태초안 입력값, 다이얼로그 상태, 대기 중 액션, 뷰 토글

소스 경로

상태 경계소스 경로
Query 기본값과 서버/브라우저 클라이언트 분리lumie-frontend/src/shared/lib/query-client.ts
루트 Query providerlumie-frontend/src/shared/providers/QueryProvider.tsx
orval fetch 브리지lumie-frontend/src/shared/api/orval-mutator.ts
공유 API 요청과 refresh 재시도lumie-frontend/src/shared/api/base.ts
서버 인증 상태lumie-frontend/src/entities/session/api/getServerUser.ts
세션 캐시와 공유 인증 접근 브리지lumie-frontend/src/shared/lib/sessionCache.ts, lumie-frontend/src/shared/api/sessionAccessor.ts
인증 리다이렉트 사유 매핑lumie-frontend/src/shared/lib/authRedirectReason.ts
클라이언트 인증 모달 스토어lumie-frontend/src/shared/providers/AuthModalProvider.tsx
서버에서 주입한 공개 테넌트 컨텍스트lumie-frontend/src/entities/tenant/providers/PublicTenantContextHydrator.tsx
URL 기반 목록 상태lumie-frontend/src/entities/student/model/search-params.ts, lumie-frontend/src/shared/lib/useUrlPageParam.ts

TanStack Query를 이용한 서버 상태

QueryProvider는 루트에서 QueryClientProvider를 한 번만 마운트합니다. getQueryClient()는 다음을 사용합니다.

  • 서버 요청마다 새로운 query client
  • 브라우저에서는 안정적인 singleton query client

기본 Query 동작은 src/shared/lib/query-client.ts에서 중앙 정의됩니다.

  • 서버 staleTime은 한 번의 SSR 패스에서 중복 fetch를 피하기 위해 Infinity
  • 브라우저 staleTime은 5분
  • Query 캐시 garbage collection은 10분 동안 warm 상태 유지
  • window-focus refetch는 기본 비활성화
  • 오래 실행되는 polling은 opt-in이며, 제한 없이 백그라운드 refetch를 계속하는 대신 최종 상태 또는 timeout에서 멈춰야 합니다

대부분의 API 읽기와 쓰기는 src/shared/api/orval-mutator.ts로 위임하는 orval 생성 hook에서 옵니다. 이 mutator는 다시 공유 apiRequest()apiUpload() 헬퍼를 사용합니다. 생성된 클라이언트만으로 부족한 경우 수동 hook도 있지만, 같은 공유 fetch 기본 요소를 사용합니다.

세션 및 인증 상태

인증된 사용자 상태는 커스텀 전역 스토어에 저장하지 않습니다.

  • 서버 레이아웃은 접근 제어를 위해 getServerUser()를 호출합니다.
  • 클라이언트 컴포넌트는 useMe() 또는 useMeQuery()를 사용합니다.
  • apiRequest()tryRefreshToken()으로 401 재시도를 처리하며, 이 함수는 단순 boolean이 아니라 타입이 있는 성공/실패 결과를 반환합니다.
  • 백엔드의 AUTH_017은 같은 디바이스 카테고리에서 다른 로그인이 발생해 현재 브라우저 세션이 교체되었다는 뜻입니다. 공유 API 레이어는 이 코드를 URL-safe reason=session_replaced 로그인 안내로 매핑합니다.
  • sessionAccessorsessionCacheshared가 엔티티 코드를 import하지 않고도 공유 API 코드에서 테넌트 slug, 테넌트 리다이렉트 컨텍스트를 읽고 세션 상태를 지울 수 있게 합니다.
  • sessionCache.getTenantRedirectContext()는 현재 테넌트 query 데이터, 캐시된 /v1/me의 백엔드 검증 테넌트 URL 필드, 콜드 인증 경계 리다이렉트용 브라우저 host/path 대체 경로 순서로 테넌트 컨텍스트를 선택합니다.

이렇게 하면 인증 관심사를 여러 스토어에 중복시키지 않고 Query 레이어 가까이에 둘 수 있습니다.

Zustand를 이용한 공유 클라이언트 UI 상태

Zustand는 현재 두 개의 좁은 클라이언트 브리지에 사용됩니다.

src/shared/providers/AuthModalProvider.tsx는 다음을 유지합니다.

  • 로그인용 또는 회원가입용으로 모달이 열려 있는지
  • 정제된 callbackUrl
  • 열기, 초기화, URL 기반 초기화를 위한 액션

src/entities/tenant/providers/PublicTenantContextHydrator.tsx는 현재 /:customId 랜딩 페이지에 대해 서버가 해석한 공개 테넌트를 유지합니다. usePublicTenantAuthContext()는 브라우저의 공개 테넌트 query로 대체하기 전에 이 store를 먼저 읽기 때문에, 공유 인증 모달은 랜딩 페이지를 렌더링할 때 검증된 것과 같은 tenantSlug를 사용할 수 있습니다.

두 store의 범위는 의도적으로 좁습니다. 대부분의 페이지 수준 UI 상태는 여전히 해당 컴포넌트 로컬에 둡니다.

React Hook Form을 이용한 폼 상태

폼은 zodResolver를 통한 Zod 기반 검증과 함께 React Hook Form으로 구성됩니다.

코드베이스의 현재 패턴에는 다음이 포함됩니다.

  • 로그인이나 프로필 업데이트 같은 단일 화면 폼
  • CRUD 플로우용 모달 또는 드로어 폼
  • 온보딩 같은 FormProvider 기반 다단계 폼
  • 조합된 폼 섹션 내부 파생 필드 동작용 useWatch() 또는 useFormContext()

검증 스키마는 대개 엔티티 또는 feature model 레이어에 두어 폼 UI를 얇게 유지합니다.

URL 상태

목록이 많은 화면은 URL을 상태 모델의 일부로 취급합니다.

  • 학생 목록은 useStudentListSearchParams()parseStudentListParams()를 사용합니다
  • Q&A는 parseQnaListParams()와 search param 업데이트 헬퍼를 사용합니다
  • 페이지 전용 목록은 useUrlPageParam()을 사용합니다

중요한 규칙은 URL 파싱과 React Query key 생성이 같은 헬퍼를 공유한다는 점입니다. 이렇게 하면 라우트 전환과 목록 뷰 사이의 refetch 불일치를 줄일 수 있습니다.

사용하지 않는 것

  • Redux 스토어 없음
  • MobX 없음
  • 가변 제품 상태용 범용 React Context 상태 레이어 없음

React Context는 provider 성격의 관심사에만 사용하고, 가변 앱 데이터는 Query, 폼, Zustand, 또는 로컬 컴포넌트 상태로 처리합니다.

검증

cd lumie-frontend
rg -n "staleTime|QueryClientProvider|tryRefreshToken|sessionAccessor|create\\(|useStudentListSearchParams|useUrlPageParam" \
src
npm run lint

성공 기준은 grep이 query 기본값, refresh/세션 상태, Zustand 스토어 생성, URL 상태 헬퍼를 찾아내고, npm run lint가 ESLint 오류 없이 완료되는 것입니다. 세션 교체 흐름은 npm run test:unit -- base AuthModalProvider sessionCache tenantRedirect가 인증 실패 사유, 테넌트 인식 로그인 URL, 콜드 캐시 테넌트 리다이렉트 컨텍스트를 다루는 집중 단위 테스트를 통과해야 합니다.

관련 페이지