Skip to content

Comments

[refactor] 검색창 입력시 리렌더링되는 문제를 해결한다#714

Merged
seongwon030 merged 9 commits intodevelop-fefrom
feature/#712-optimize-rerender-main-page-MOA-208
Sep 1, 2025
Merged

[refactor] 검색창 입력시 리렌더링되는 문제를 해결한다#714
seongwon030 merged 9 commits intodevelop-fefrom
feature/#712-optimize-rerender-main-page-MOA-208

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Aug 31, 2025

#️⃣연관된 이슈

ex) #712

📝작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지/동영상 첨부 가능)

문제상황

2025-09-01.02.53.27.mov

메인페이지 검색어를 입력할 시 해당 페이지의 모든 컴포넌트가 리렌더링되는 문제가 있었습니다.
Context api의 고질적인 리렌더링 문제 때문에 발생하였습니다. 참고자료

구독중인 context 상태가 바뀔 때마다, 해당 context를 구독 중인 하위 컴포넌트들이 리렌더링 된다는 것이었어요.

해결방법

fe자료실-zustand사용이유

적용 이후

2025-09-01.02.52.20.mov

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • Refactor
    • 검색 상태 관리를 전역 컨텍스트에서 경량 스토어 기반으로 전환하여 렌더링 최소화와 상태 초기화 일관성을 개선했습니다. 앱 초기 트리를 단순화하고, 메인 페이지·카테고리 버튼·검색 박스·헤더는 기존 동작을 유지하되 검색 초기화가 통합되어 홈 이동·카테고리 전환 시 더 예측 가능하게 동작합니다.
  • Chores
    • 상태 관리 라이브러리 의존성을 추가했습니다.
    • 스토리북에서 불필요한 컨텍스트 데코레이터를 제거했습니다.

- React Context에서 Zustand로 마이그레이션
- 검색창 입력 시 헤더와 카테고리 리스트 리렌더링 방지
- 개별 상태 구독을 통한 컴포넌트 최적화
- MainPage: keyword, isSearching만 구독하도록 변경
- SearchBox: useSearchInput hook 사용으로 검색 입력 관련 상태만 구독
- CategoryButtonList: getState() 직접 사용으로 상태 구독 제거
- useHeaderService: getState() 직접 사용으로 상태 구독 제거
- 검색창 입력 시 Header, CategoryButtonList 리렌더링 방지
@seongwon030 seongwon030 self-assigned this Aug 31, 2025
@seongwon030 seongwon030 added 🔨 Refactor 코드 리팩토링 💻 FE Frontend labels Aug 31, 2025
@vercel
Copy link

vercel bot commented Aug 31, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
moadong Ready Ready Preview Comment Sep 1, 2025 1:46am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 31, 2025

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

검색 상태 관리가 React Context에서 Zustand 스토어로 교체되었고, 관련 컴포넌트와 서비스가 새 스토어 셀렉터/액션을 사용하도록 업데이트되었습니다. SearchContext 모듈이 제거되고, 루트(App)와 스토리에서 해당 프로바이더가 삭제되었습니다. 패키지 의존성에 zustand가 추가되었습니다.

Changes

Cohort / File(s) Summary
상태관리 마이그레이션: Context → Zustand
frontend/src/context/SearchContext.tsx
SearchContext 모듈 전체 제거(Provider, useSearch 훅 삭제).
Zustand 스토어 추가
frontend/src/store/useSearchStore.ts
zustand 기반 검색 스토어 추가: 상태(keyword, inputValue, isSearching), 액션(setters, resetSearch), 셀렉터 훅(useSearchKeyword, useSearchIsSearching, useSearchInput) 정의.
컴포넌트: 스토어 적용
frontend/src/pages/MainPage/MainPage.tsx, frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx, frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx, frontend/src/services/header/useHeaderService.ts
Context 훅 사용 제거 후 스토어 훅/액션으로 대체. 카테고리 클릭/홈 이동 시 resetSearch 사용. MainPage는 셀렉터 훅으로 keyword/isSearching 구독. SearchBox는 useSearchInput 사용.
루트/스토리: Provider 제거
frontend/src/App.tsx, frontend/src/components/common/Header/Header.stories.tsx
SearchProvider 및 관련 데코레이터 제거. 트리에서 QueryClientProvider → CategoryProvider → BrowserRouter 구성 유지.
의존성
frontend/package.json
zustand@^5.0.8 추가. JSON 수정(콤마 정리).

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Header
  participant SearchBox
  participant MainPage
  participant Router as BrowserRouter
  participant Store as useSearchStore (Zustand)

  rect rgba(220,235,255,0.4)
  note over SearchBox,Store: 검색 실행 흐름 (변경: Context → Store)
  User->>SearchBox: 키워드 입력/검색 제출
  SearchBox->>Store: setInputValue / setKeyword / setIsSearching(true)
  SearchBox->>Router: navigate(/)
  Router-->>MainPage: 라우트 렌더
  MainPage->>Store: useSearchKeyword / useSearchIsSearching (select)
  MainPage-->>User: 검색 결과 렌더
  end

  rect rgba(225,255,225,0.4)
  note over Header,Store: 홈 이동 시 초기화
  User->>Header: 로고 클릭
  Header->>Router: navigate(/)
  Header->>Store: resetSearch()
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
헤더 메모이제이션 MOA-208 메모이제이션 적용 코드가 확인되지 않습니다.
검색창 메모이제이션 MOA-208 메모이제이션 적용 코드가 확인되지 않습니다.
리렌더링 성능 측정 MOA-208 측정 도구/지표/로그가 추가되지 않았습니다.

Assessment against linked issues: Out-of-scope changes

(해당 없음)

Possibly related issues

Possibly related PRs

Suggested reviewers

  • lepitaaar
  • oesnuj
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#712-optimize-rerender-main-page-MOA-208

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
frontend/src/context/useSearchStore.ts (1)

56-63: 불필요한 다중 구독을 줄이기 위한 미세 개선

현재 4번(useSearchStore) 호출로 각각 구독합니다. setter는 안정 참조이므로 문제는 크지 않지만, 하나의 셀렉터로 묶으면 구독 수를 줄일 수 있습니다.

선호안(버전 호환되는 얕은 비교 유틸 사용 시):

-export const useSearchInput = () => {
-  const inputValue = useSearchStore((state) => state.inputValue);
-  const setInputValue = useSearchStore((state) => state.setInputValue);
-  const setKeyword = useSearchStore((state) => state.setKeyword);
-  const setIsSearching = useSearchStore((state) => state.setIsSearching);
-
-  return { inputValue, setInputValue, setKeyword, setIsSearching };
-};
+// shallow 비교 유틸이 프로젝트에 이미 있다면 적용 고려
+export const useSearchInput = () =>
+  useSearchStore((s) => ({
+    inputValue: s.inputValue,
+    setInputValue: s.setInputValue,
+    setKeyword: s.setKeyword,
+    setIsSearching: s.setIsSearching,
+  }));
frontend/src/components/common/Header/Header.stories.tsx (1)

15-15: Storybook 데코레이터 단순화 LGTM

SearchContext 의존 제거와 일관됩니다. 필요 시 MemoryRouter로도 대체 가능하지만 현재 구성으로 충분합니다.

frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (1)

40-47: 검색 상태 리셋을 한 번에 처리하고 순서를 조정해 불필요한 중간 fetch를 방지하세요

현재는 setKeyword → setInputValue → setIsSearching(false) → setSelectedCategory 순으로 호출됩니다. MainPage의 searchCategory = isSearching ? 'all' : selectedCategory 로직상 isSearching을 먼저 false로 바꾸면 직전 selectedCategory로 한 번, 이후 새로운 selectedCategory로 또 한 번 요청이 발생할 수 있습니다. 또한 store를 3회 갱신해 구독자에 불필요한 notify가 누적될 수 있습니다.

  • 권장: (1) 먼저 setSelectedCategory를 호출해서 선택 카테고리를 확정하고, (2) useSearchStore.setState로 검색 상태를 한 번에 리셋하세요.

적용 diff:

-    const { setKeyword, setInputValue, setIsSearching } =
-      useSearchStore.getState();
-    setKeyword('');
-    setInputValue('');
-    setIsSearching(false);
-
-    setSelectedCategory(category.id);
+    // 1) 카테고리를 먼저 변경하여 중간 불필요한 fetch를 방지
+    setSelectedCategory(category.id);
+    // 2) 검색 상태 리셋을 단일 setState로 묶어 구독자 알림/리렌더를 최소화
+    useSearchStore.setState({ keyword: '', inputValue: '', isSearching: false });

추가로, 재사용을 위해 store에 액션을 두는 것도 좋습니다:

// frontend/src/context/useSearchStore.ts
// store 생성부에 추가
resetSearch: () => set({ keyword: '', inputValue: '', isSearching: false });

그 후 이 파일에서는:

useSearchStore.getState().resetSearch();

처럼 호출하면 됩니다.

frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (1)

8-9: 키워드/검색중 상태를 단일 업데이트로 묶어 리렌더/재요청 최소화

setKeyword(inputValue)setIsSearching(true)가 분리 호출됩니다. 두 값은 논리적으로 한 동작(검색 실행)에 속하므로 하나의 setState로 묶는 편이 좋습니다. 또한 setSelectedCategory('all')를 먼저 호출하면 검색 파라미터가 안정화된 뒤 단일 요청으로 이어질 가능성이 큽니다.

옵션 A: setState 직접 사용

-    setKeyword(inputValue);
-    setSelectedCategory('all');
-    setIsSearching(true);
+    setSelectedCategory('all');
+    useSearchStore.setState({ keyword: inputValue, isSearching: true });

추가 import:

- import { useSearchInput } from '@/context/useSearchStore';
+ import { useSearchInput, useSearchStore } from '@/context/useSearchStore';

옵션 B: store에 액션 추가 (권장)

// useSearchStore.ts
startSearch: (keyword: string) => set({ keyword, isSearching: true });

이 파일:

setSelectedCategory('all');
useSearchInput().startSearch(inputValue);

두 옵션 모두 렌더/요청 횟수를 줄이는 데 도움이 됩니다. 네트워크 탭에서 검색 실행 시 요청이 1회로 유지되는지 확인 부탁드립니다.

Also applies to: 24-27

frontend/src/pages/MainPage/MainPage.tsx (1)

27-32: 'all'/'OPEN'과 같은 매직 문자열을 상수로 통일

여러 파일에서 동일 리터럴을 반복하면 오타·불일치가 생기기 쉽습니다. 예: ALL_CATEGORY = 'all', RECRUITMENT_OPEN = 'OPEN'. 공용 constants로 분리해 재사용하면 의미가 명확해지고 변경에도 강해집니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6be7d37 and 8d0efd6.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • frontend/package.json (1 hunks)
  • frontend/src/App.tsx (1 hunks)
  • frontend/src/components/common/Header/Header.stories.tsx (1 hunks)
  • frontend/src/context/SearchContext.tsx (0 hunks)
  • frontend/src/context/useSearchStore.ts (1 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/services/header/useHeaderService.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • frontend/src/context/SearchContext.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/components/common/Header/Header.stories.tsx
  • frontend/src/App.tsx
  • frontend/src/services/header/useHeaderService.ts
  • frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx
  • frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx
  • frontend/src/context/useSearchStore.ts
  • frontend/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/components/common/Header/Header.stories.tsx
  • frontend/src/App.tsx
  • frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx
  • frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx
  • frontend/src/pages/MainPage/MainPage.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-19T05:09:10.702Z
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.

Applied to files:

  • frontend/src/pages/MainPage/MainPage.tsx
🧬 Code graph analysis (5)
frontend/src/App.tsx (2)
frontend/src/context/CategoryContext.tsx (1)
  • CategoryProvider (20-52)
frontend/src/context/AdminClubContext.tsx (1)
  • AdminClubProvider (15-28)
frontend/src/services/header/useHeaderService.ts (1)
frontend/src/context/useSearchStore.ts (1)
  • useSearchStore (13-22)
frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (1)
frontend/src/context/useSearchStore.ts (1)
  • useSearchInput (56-63)
frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (1)
frontend/src/context/useSearchStore.ts (1)
  • useSearchStore (13-22)
frontend/src/pages/MainPage/MainPage.tsx (1)
frontend/src/context/useSearchStore.ts (2)
  • useSearchKeyword (44-48)
  • useSearchIsSearching (50-54)
🔇 Additional comments (5)
frontend/package.json (1)

41-43: Zustand 의존성 추가가 올바르게 반영되었습니다 — 락파일 재생성 후 CI 캐시/빌드만 확인하세요.

frontend/src/App.tsx (1)

28-94: SearchProvider 제거 및 라우팅 트리 정리 LGTM

Zustand 전환에 맞춘 Provider 제거가 적절하며, Category/Admin 컨텍스트 경계도 명확합니다.

frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx (1)

10-10: LGTM — Zustand로 전환하며 불필요한 전역 상태 구독 제거

Context 구독 없이 store 접근으로 해당 컴포넌트의 리렌더를 막는 방향이 맞습니다.

frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (1)

1-1: LGTM — useSearchInput 훅으로 교체 적절

입력값만 구독하도록 분리되어 리렌더 범위를 줄이는 방향이 맞습니다.

frontend/src/pages/MainPage/MainPage.tsx (1)

2-5: LGTM — selector 단위 훅 분리로 구독 범위 축소

useSearchKeyword/useSearchIsSearching로 필요한 조각만 구독하는 구조가 본 PR의 목적(리렌더 최소화)에 부합합니다.

- keyword, inputValue, isSearching을 한 번에 초기화하는 resetSearch 함수 구현
- 카테고리 변경, 홈 이동 등에서 검색 상태 리셋 시 편의성 향상
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
frontend/src/store/useSearchStore.ts (4)

12-24: 초기값 중복 제거 및 리셋 안정성: INITIAL_STATE 도입 제안

초기값이 여러 군데에 중복 정의돼 있어(초기화와 resetSearch) 변경 시 누락 위험이 있습니다. 상수로 묶어 단일 소스 유지가 안전합니다.

 interface SearchStore {
   keyword: string;
   setKeyword: (keyword: string) => void;
   inputValue: string;
   setInputValue: (value: string) => void;
   isSearching: boolean;
   setIsSearching: (isSearching: boolean) => void;
   resetSearch: () => void;
 }
 
+// 초기 상태를 단일 소스로 관리
+const INITIAL_STATE: Pick<SearchStore, 'keyword' | 'inputValue' | 'isSearching'> = {
+  keyword: '',
+  inputValue: '',
+  isSearching: false,
+};
+
 export const useSearchStore = create<SearchStore>()(
   subscribeWithSelector((set) => ({
-    keyword: '',
+    ...INITIAL_STATE,
-    setKeyword: (keyword) => set({ keyword }),
-    inputValue: '',
+    setKeyword: (keyword) => set({ keyword }),
     setInputValue: (value) => set({ inputValue: value }),
-    isSearching: false,
-    setIsSearching: (isSearching) => set({ isSearching }),
-    resetSearch: () => set({ keyword: '', inputValue: '', isSearching: false }),
+    setIsSearching: (isSearching) => set({ isSearching }),
+    resetSearch: () => set(INITIAL_STATE),
   })),
 );

38-45: SearchBox 편의성 향상: resetSearch도 함께 노출

SearchBox에서 입력 초기화 시 store 외부 접근(getState) 없이 처리할 수 있게 resetSearch를 함께 노출하면 사용성이 좋아집니다. 함수 레퍼런스는 안정적이라 리렌더 영향도 없습니다.

 export const useSearchInput = () => {
   const inputValue = useSearchStore((state) => state.inputValue);
   const setInputValue = useSearchStore((state) => state.setInputValue);
   const setKeyword = useSearchStore((state) => state.setKeyword);
   const setIsSearching = useSearchStore((state) => state.setIsSearching);
+  const resetSearch = useSearchStore((state) => state.resetSearch);
 
-  return { inputValue, setInputValue, setKeyword, setIsSearching };
+  return { inputValue, setInputValue, setKeyword, setIsSearching, resetSearch };
 };

32-36: 네이밍 미세 개선 제안

useSearchIsSearching 보다는 useSearchStatus 같은 이름이 읽기 자연스럽습니다. 강제사항은 아니니 참고만 부탁드립니다.


1-3: devtools 미들웨어 추가 및 subscribeWithSelector 사용 여부 검토

  • useSearchStore 생성부에 devtools 미들웨어를 래핑하여 개발 환경에서만 활성화하세요.
-import { subscribeWithSelector } from 'zustand/middleware';
+import { subscribeWithSelector, devtools } from 'zustand/middleware';

 export const useSearchStore = create<SearchStore>()(
-  subscribeWithSelector((set) => ({
+  subscribeWithSelector(
+    devtools((set) => ({
       ...INITIAL_STATE,
       setKeyword: (keyword) => set({ keyword }),
       setInputValue: (value) => set({ inputValue: value }),
       setIsSearching: (isSearching) => set({ isSearching }),
       resetSearch: () => set(INITIAL_STATE),
-  })),
+    }), { name: 'search-store', enabled: process.env.NODE_ENV !== 'production' })
+  ),
 );
  • 프로젝트 전반에 subscribeWithSelector 호출은 해당 파일 외에 존재하지 않으므로, selector 기반 구독이 실제로 필요 없다면 미들웨어 자체를 제거하는 것도 고려하세요.
frontend/src/pages/MainPage/MainPage.tsx (2)

60-70: 중첩 삼항식은 IIFE/if-else로 풀어 가독성 향상

가이드라인(중첩/복잡한 삼항 지양)에 따라 조건 분기를 풀면 읽기 쉬워집니다.

-          {isLoading ? (
-            <Spinner />
-          ) : isEmpty ? (
-            <Styled.EmptyResult>
-              앗, 조건에 맞는 동아리가 없어요.
-              <br />
-              다른 키워드나 조건으로 다시 시도해보세요!
-            </Styled.EmptyResult>
-          ) : (
-            <Styled.CardList>{clubList}</Styled.CardList>
-          )}
+          {(() => {
+            if (isLoading) return <Spinner />;
+            if (isEmpty) {
+              return (
+                <Styled.EmptyResult>
+                  앗, 조건에 맞는 동아리가 없어요.
+                  <br />
+                  다른 키워드나 조건으로 다시 시도해보세요!
+                </Styled.EmptyResult>
+              );
+            }
+            return <Styled.CardList>{clubList}</Styled.CardList>;
+          })()}

38-41: useMemo 의존성 정리(불필요 의존 제거)

clubList는 clubs만으로 결정됩니다. hasData를 의존성에서 제거해 불필요 재계산을 줄일 수 있습니다.

-  const clubList = useMemo(() => {
-    if (!hasData) return null;
-    return clubs.map((club: Club) => <ClubCard key={club.id} club={club} />);
-  }, [clubs, hasData]);
+  const clubList = useMemo(() => {
+    if (!clubs?.length) return null;
+    return clubs.map((club: Club) => <ClubCard key={club.id} club={club} />);
+  }, [clubs]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8d0efd6 and f6ed583.

📒 Files selected for processing (5)
  • 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/services/header/useHeaderService.ts (2 hunks)
  • frontend/src/store/useSearchStore.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/pages/MainPage/components/CategoryButtonList/CategoryButtonList.tsx
  • frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx
  • frontend/src/services/header/useHeaderService.ts
🧰 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/useSearchStore.ts
  • frontend/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/pages/MainPage/MainPage.tsx
🧬 Code graph analysis (1)
frontend/src/pages/MainPage/MainPage.tsx (1)
frontend/src/store/useSearchStore.ts (2)
  • useSearchKeyword (26-30)
  • useSearchIsSearching (32-36)
🔇 Additional comments (4)
frontend/src/store/useSearchStore.ts (1)

14-24: Zustand로의 전환 및 슬라이스 선택자 사용, 방향성 좋습니다

검색 관련 리렌더링을 슬라이스 단위로 한정하는 접근이 명확하고, 메인 페이지의 불필요 리렌더링을 줄이는 데 효과적입니다.

frontend/src/pages/MainPage/MainPage.tsx (3)

24-26: 스토어 슬라이스 선택 사용으로 리렌더링 축소, 방향성 적절

keyword와 isSearching만 구독해 메인 페이지 리렌더 범위를 최소화했습니다. 의도와 구현이 일치합니다.


28-29: 검색 중 카테고리 무시 정책 확인 필요

isSearching일 때 searchCategory를 'all'로 강제하는 정책이 의도(검색 시 카테고리 필터 무시)와 일치하는지 PO/기획 확인 부탁드립니다. 기존 사용자 기대(카테고리+키워드 AND 검색)와 다를 수 있습니다.


30-35: 입력 타이핑 시 네트워크 트래픽 증가 여부 확인

useGetCardList가 keyword 기준으로만 요청 키를 구성한다면(의도대로 제출 시점에만 setKeyword), 타이핑 중에는 재요청이 발생하지 않습니다. SearchBox에서 setKeyword 호출이 제출 시점에만 일어나는지 한 번만 점검 부탁드립니다.

Copy link
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

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

zustand를 이용한 전역상태관리 좋습니다. zustand 사용 이유도 상세히 작성해주셔서 좋네요!!
조금씩 context에서 zustand로 리팩토링 해보죠

Copy link
Member

@oesnuj oesnuj left a comment

Choose a reason for hiding this comment

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

zustand 도입 좋습니다
이유와 함께 노션에 정리 해주신 부분이 좋았습니다 ㅎㅎ
좋은 영향 받아서 저도 애니메이션 관련 학습 정리하고 있습니다~~

@seongwon030 seongwon030 merged commit bf5bbe7 into develop-fe Sep 1, 2025
5 checks passed
@seongwon030 seongwon030 deleted the feature/#712-optimize-rerender-main-page-MOA-208 branch September 1, 2025 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend 🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] MOA-208 메인페이지 검색창 입력시 리렌더링되는 문제를 해결한다

3 participants