본문으로 건너뛰기

상태 관리

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

상태 계층

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

소스 경로

상태 경계소스 경로
Query 기본값과 서버/브라우저 client 분리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/providers/AuthModalProvider.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 cache garbage collection은 10분 동안 warm 상태 유지
  • window-focus refetch는 기본 비활성화

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

세션 및 인증 상태

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

  • 서버 레이아웃은 접근 제어를 위해 getServerUser()를 호출합니다.
  • 클라이언트 컴포넌트는 useMe() 또는 useMeQuery()를 사용합니다.
  • apiRequest()tryRefreshToken()으로 401 재시도를 처리합니다.
  • sessionAccessorsessionCacheshared에서 엔티티 코드를 import하지 않고도 공유 API 코드가 tenant slug를 읽고 세션 상태를 지우게 해줍니다.

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

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

Zustand는 현재 src/shared/providers/AuthModalProvider.tsx의 인증 모달에 사용됩니다. 이 스토어는 다음을 유지합니다.

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

스토어 범위는 의도적으로 좁습니다. 대부분의 페이지 수준 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 type-check

성공 기준은 grep이 query 기본값, refresh/세션 상태, Zustand 스토어 생성, URL 상태 헬퍼를 찾아내고, npm run type-check가 TypeScript 오류 없이 완료되는 것입니다.

관련 페이지