[refactor] 카테고리 context를 zustand기반으로 마이그레이션#729
Conversation
- useCategoryStorage hook 및 storage 이벤트 리스너 제거 - persist 미들웨어로 sessionStorage 자동 동기화
- CategoryButtonList, SearchBox, MainPage에서 쓰이던 것 변경
- setSelectedCategory제거
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
Context 제거frontend/src/context/CategoryContext.tsx |
카테고리 컨텍스트, CategoryProvider, useCategory 전면 삭제(세션 스토리지 동기화 및 storage 이벤트 리스너 포함). |
스토어 도입frontend/src/store/useCategoryStore.ts |
Zustand 스토어 추가: selectedCategory, setSelectedCategory 제공. persist로 sessionStorage(category-storage)에 지속. useSelectedCategory 편의 훅 제공. |
앱 래퍼 업데이트frontend/src/App.tsx |
CategoryProvider 래핑 제거. QueryClientProvider → BrowserRouter → GlobalStyles/Routes 구성 유지. |
소비자 컴포넌트 마이그레이션frontend/src/pages/MainPage/MainPage.tsx |
useCategory → useSelectedCategory로 교체. 이 컴포넌트에서는 selectedCategory만 사용하도록 변경. |
소비자 컴포넌트 마이그레이션frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx |
useCategory → useSelectedCategory. setSelectedCategory를 스토어에서 사용. 클릭 처리 동일. |
소비자 컴포넌트 마이그레이션frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx |
useCategory → useSelectedCategory. 검색 시 setSelectedCategory 사용 로직 유지. |
Sequence Diagram(s)
sequenceDiagram
participant UI as MainPage / SearchBox / CategoryButtonList
participant Store as useCategoryStore (Zustand)
participant Persist as sessionStorage ("category-storage")
rect rgba(230,245,255,0.5)
note over UI,Store: 카테고리 상태 접근/변경 (New)
UI->>Store: select()로 selectedCategory 구독
UI->>Store: setSelectedCategory(id)
Store-->>UI: selectedCategory 업데이트(구독자 통지)
end
rect rgba(240,255,240,0.5)
note over Store,Persist: 상태 지속 (New)
Store-->>Persist: selectedCategory 저장(JSON)
Persist-->>Store: 초기화 시 하이드레이션
end
Estimated code review effort
🎯 2 (Simple) | ⏱️ ~10 minutes
Assessment against linked issues
| Objective | Addressed | Explanation |
|---|---|---|
| 카테고리 버튼 상태를 Context API에서 Zustand로 변경 [MOA-215/#720] | ✅ |
Assessment against linked issues: Out-of-scope changes
(없음)
Possibly related issues
(없음)
Possibly related PRs
- [feature] 카테고리 선택 상태 Context 전역 관리로 변경 및 페이지 이동 시 상태 유지 #512 — 기존 컨텍스트/프로바이더 추가 변경과 동일 영역을 반대로 대체하므로 충돌 가능성이 높은 직접 관련 변경.
- [feature] 검색 시 전체동아리 기준으로 검색된다 #616 — 검색 중 카테고리 처리(isSearching)와 메인/검색 컴포넌트 동작을 함께 다뤄 본 PR의 소비자 변경과 연관.
- [refactor] 카테고리 버튼 클릭 로깅 통일 #638 — CategoryButtonList 클릭 트래킹 로직을 수정한 PR로, 본 PR의 동일 파일 훅 교체와 겹침.
Suggested reviewers
- lepitaaar
- oesnuj
- suhyun113
✨ Finishing Touches
- 📝 Generate Docstrings
🧪 Generate unit tests
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
refactor/#720-category-button-rerender-solved-MOA-215
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (8)
frontend/src/store/useCategoryStore.ts (2)
1-6: 매직 스트링 상수화 + shallow로 중복 구독 제거 + persist 버전 지정 제안
- 'all', 'category-storage'를 상수화하면 의도와 재사용성이 명확해집니다.
- useSelectedCategory가 동일 스토어에 2번 구독합니다. shallow를 써서 한 번의 셀렉션으로 합치면 불필요한 구독을 줄일 수 있습니다.
- persist에 version을 지정해 추후 마이그레이션을 수월하게 하세요.
import { create } from 'zustand'; +import { shallow } from 'zustand/shallow'; import { persist, subscribeWithSelector, createJSONStorage, } from 'zustand/middleware'; +export const CATEGORY_STORAGE_KEY = 'category-storage'; +export const DEFAULT_CATEGORY = 'all'; + export const useCategoryStore = create<CategoryStore>()( subscribeWithSelector( persist( (set) => ({ - selectedCategory: 'all', + selectedCategory: DEFAULT_CATEGORY, setSelectedCategory: (category) => set({ selectedCategory: category }), }), { - name: 'category-storage', + name: CATEGORY_STORAGE_KEY, + version: 1, storage: createJSONStorage(() => sessionStorage), }, ), ), ); -export const useSelectedCategory = () => { - const selectedCategory = useCategoryStore((state) => state.selectedCategory); - const setSelectedCategory = useCategoryStore( - (state) => state.setSelectedCategory, - ); - return { selectedCategory, setSelectedCategory }; -}; +export const useSelectedCategory = () => + useCategoryStore( + (s) => ({ + selectedCategory: s.selectedCategory, + setSelectedCategory: s.setSelectedCategory, + }), + shallow, + );Also applies to: 17-24, 28-34
21-23: sessionStorage 의존성: SSR/테스트 환경 호환성 확인 필요현재는 SPA라 문제 없겠지만 SSR(예: Next.js) 도입 시 스토리지 접근 시점/하이드레이션 순서에 주의가 필요합니다. 필요 시 클라이언트에서만 storage를 주입하거나, 하이드레이션 콜백(onFinishHydration)으로 초기 렌더를 제어하는 패턴을 고려하세요.
SSR 계획이 있으시면 알려주세요. SSR-safe 초기화 스니펫을 준비해드릴게요.
frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (2)
31-31: setter만 사용하는 컴포넌트는 무(無)구독 패턴으로 전환 가능이 컴포넌트는 상태를 읽지 않고 setter만 사용하므로 getState로 액션을 꺼내오면 구독 자체를 없앨 수 있습니다.
-import { useSelectedCategory } from '@/store/useCategoryStore'; +import { useCategoryStore } from '@/store/useCategoryStore'; ... - const { setSelectedCategory } = useSelectedCategory(); + const setSelectedCategory = useCategoryStore.getState().setSelectedCategory;Also applies to: 11-12
20-28: 불변성 보장: 카테고리 목록을 readonly로 선언런타임 변경 가능성을 제거해 실수 수정을 방지합니다.
-const clubCategories: Category[] = [ +const clubCategories: readonly Category[] = [frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (2)
1-5: 이벤트명 상수화로 일관성 유지다른 파일에서 EVENT_NAME을 쓰고 있으므로 여기서도 동일 상수로 맞추는 것을 권장합니다. 없다면 constants에 추가 부탁드립니다.
import { useSearchInput } from '@/store/useSearchStore'; import { useSelectedCategory } from '@/store/useCategoryStore'; import useMixpanelTrack from '@/hooks/useMixpanelTrack'; import SearchField from '@/components/common/SearchField/SearchField'; import { useLocation, useNavigate } from 'react-router-dom'; +import { EVENT_NAME } from '@/constants/eventName'; ... - trackEvent('Search Executed', { + trackEvent(EVENT_NAME.SEARCH_EXECUTED, { inputValue: inputValue, page: currentPage, });Also applies to: 28-31
37-37: 불필요한 람다 제거함수 참조를 그대로 넘겨도 됩니다.
- onChange={(v) => setInputValue(v)} + onChange={setInputValue}frontend/src/pages/MainPage/MainPage.tsx (2)
26-29: 매직 스트링 상수화로 의도 명확화'OPEN' / 'all'을 상수로 두면 의미가 드러나고 오타를 줄일 수 있습니다.
+const ALL = 'all' as const; +const OPEN = 'OPEN' as const; ... - const recruitmentStatus = isFilterActive ? 'OPEN' : 'all'; - const division = 'all'; - const searchCategory = isSearching ? 'all' : selectedCategory; + const recruitmentStatus = isFilterActive ? OPEN : ALL; + const division = ALL; + const searchCategory = isSearching ? ALL : selectedCategory;
60-70: 중첩 삼항 제거로 가독성 개선가이드라인(중첩/복잡한 삼항 지양)에 맞춰 렌더링 분기를 함수로 분리하길 권장합니다.
const clubList = useMemo(() => { if (!hasData) return null; return clubs.map((club: Club) => <ClubCard key={club.id} club={club} />); }, [clubs, hasData]); + + const renderContent = () => { + if (isLoading) return <Spinner />; + if (isEmpty) { + return ( + <Styled.EmptyResult> + 앗, 조건에 맞는 동아리가 없어요. + <br /> + 다른 키워드나 조건으로 다시 시도해보세요! + </Styled.EmptyResult> + ); + } + return <Styled.CardList>{clubList}</Styled.CardList>; + }; @@ - {isLoading ? ( - <Spinner /> - ) : isEmpty ? ( - <Styled.EmptyResult> - 앗, 조건에 맞는 동아리가 없어요. - <br /> - 다른 키워드나 조건으로 다시 시도해보세요! - </Styled.EmptyResult> - ) : ( - <Styled.CardList>{clubList}</Styled.CardList> - )} + {renderContent()}Also applies to: 38-41
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
frontend/src/App.tsx(2 hunks)frontend/src/context/CategoryContext.tsx(0 hunks)frontend/src/pages/MainPage/MainPage.tsx(2 hunks)frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx(2 hunks)frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx(1 hunks)frontend/src/store/useCategoryStore.ts(1 hunks)
💤 Files with no reviewable changes (1)
- frontend/src/context/CategoryContext.tsx
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/store/useCategoryStore.tsfrontend/src/App.tsxfrontend/src/pages/MainPage/components/SearchBox/SearchBox.tsxfrontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsxfrontend/src/pages/MainPage/MainPage.tsx
frontend/**/*.tsx
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/App.tsxfrontend/src/pages/MainPage/components/SearchBox/SearchBox.tsxfrontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsxfrontend/src/pages/MainPage/MainPage.tsx
🧬 Code graph analysis (4)
frontend/src/App.tsx (1)
frontend/src/context/AdminClubContext.tsx (1)
AdminClubProvider(15-28)
frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (2)
frontend/src/store/useSearchStore.ts (1)
useSearchInput(38-45)frontend/src/store/useCategoryStore.ts (1)
useSelectedCategory(28-34)
frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (1)
frontend/src/store/useCategoryStore.ts (1)
useSelectedCategory(28-34)
frontend/src/pages/MainPage/MainPage.tsx (1)
frontend/src/store/useCategoryStore.ts (1)
useSelectedCategory(28-34)
🔇 Additional comments (5)
frontend/src/store/useCategoryStore.ts (1)
13-26: Zustand + persist 구성 괜찮습니다subscribeWithSelector까지 적용해 셀렉터 단위로 리렌더를 최소화한 점이 좋습니다. 초기 상태와 액션도 명확합니다.
frontend/src/App.tsx (2)
28-86: 라우트 트리 정리 LGTMProvider 트리 간결해졌고, Admin 영역은 전용 Provider로 스코프가 분리되어 명확합니다.
28-86: CategoryProvider 관련 레거시 참조 제거 검증 완료
CategoryProvider,useCategory,@/context/CategoryContext모든 참조가 삭제된 것을 확인했습니다.frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (1)
35-44: 이벤트 트래킹/검색 리셋 순서 적절클릭 트래킹 → 검색 상태 리셋 → 카테고리 변경 흐름이 명확합니다. 👍
frontend/src/pages/MainPage/MainPage.tsx (1)
21-35: 조회 파라미터 결정 로직 깔끔합니다검색 상태에 따라 카테고리를 'all'로 덮는 분기와 쿼리 훅 연결이 명확합니다.
lepitaaar
left a comment
There was a problem hiding this comment.
수고하셧습니다. zustand 도입한거 좋은거같습니다
#️⃣연관된 이슈
📝작업 내용
목표
Profiler로 성능 측정하기
기존 context api
zustand 로 변경후
결과
5ms에서 2.6ms로 약 50% 성능 개선을 하였습니다.
zustand storage 활용하기
카테고리 context 에서 세션스토리지에 카테고리 이름을 저장하는 로직이 있었습니다.
그래서 storage라는 객체에 get, set, remove 를 정의하였습니다.
zustand-createjsonstorage를 보면
createJSONStorage()는 JSON 직렬화/역직렬화,브라우저 저장소 저장 및 복원 기능을 자동으로수행합니다.
그 결과 12줄의 코드가 1줄로 간소화되었습니다.
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
신규 기능
버그 수정
리팩터링
사용자 영향