Skip to content

[Refactor] pagination 상태 유지 개선(searchParams)#158

Open
hummingbbird wants to merge 4 commits intodevfrom
feat/#152/pagination-state-persistence
Open

[Refactor] pagination 상태 유지 개선(searchParams)#158
hummingbbird wants to merge 4 commits intodevfrom
feat/#152/pagination-state-persistence

Conversation

@hummingbbird
Copy link
Contributor

@hummingbbird hummingbbird commented Mar 10, 2026

✏️ Summary

📑 Tasks

페이지네이션의 상태가 유지되도록 리팩토링을 진행했습니다. 상세 페이지에서 '뒤로가기' 버튼을 통해 목록 페이지로 넘어왔을 때 이전 상태를 유지하려면 url에 목록에 관련된 정보들을 저장해야 하는데요, 이 때 페이지와 필터, 검색어 등 상태가 유지되어야 하는 정보들을 쿼리 파라미터로 넘겨 페이지를 옮겨다니더라도 상태가 유지되도록 구현해주었습니다.

``jsx
// matching-list-page.tsx
const [searchParams, setSearchParams] = useSearchParams();
const currentPage = Number(searchParams.get("page")) || 1;
const keyword = searchParams.get("keyword") || "";

구현 방식은 위와 같습니다. 페이지 렌더링 시 `searchParams`를 통해 쿼리 파라미터의 값을 가져오고, 존재하지 않을 경우 디폴트 값으로 설정되도록 수정해주었습니다. 추가적으로 인라인 함수로 구현되어 있던 부분도 컨벤션에 맞게 '`handle-`' 형태의 함수로 분리해주었습니다. 

현재 페이지네이션이 사용되는 페이지는 홈, 경험 목록, 매칭 경험 목록 이렇게 3가지인데요, 이중 홈과 매칭 경험 목록의 경우 검색 기능이 포함되어 있는데 검색값이 날아가는 것을 방지하기 위해 useEffect를 통해 검색값이 유지되도록 해주었습니다~

```jsx
  // 검색값 유지
  useEffect(() => {
    setSearchValue(keyword);
  }, [keyword]);

👀 To Reviewer

리팩토링 과정에서 ExperienceFilter 컴포넌트의 value의 type을 string | null로 확장해주었습니다. 그 이유는 .. 제가 안정성을 위해 범위를 너무 좁혀놨더니 오히려 결합도가 지나치게 올라가서 하나 수정하려고 130913개의 파일을 건너야 하는 불상사가 발생하여서 리팩토링 하는 김에 같이 바꿔주었습니다!

이 부분 수정하면서 http-client의 getRequestDto와, ExperienceRequestDto의 type과 getSummaryExperienceList 메소드의 type의 타입을 string으로 수정해주었습니다. 또한 유진이가 작업한 파일에서

  • src/features/experience-detail/types/experience-detail.types.ts의 type의 타입
  • src/features/experience/api/use-experience-list.query.ts의 type의 타입
    이렇게 수정되었습니다! 타입이랑, label의 기본값 정도만 수정되어서 큰 문제는 없겠지만 그래도 알아두어야 할 거 같아 전달합니다 ~

📸 Screenshot

추추의 작업 이슈로 화면녹화를 못하네요 쩝 .. 테스트 직접 해보기 ~~~~ >_<

🔔 ETC

Summary by CodeRabbit

릴리스 노트

  • 버그 수정

    • 경험 상세 화면에서 타입 검증 로직을 강화하여 안정성 개선
  • 리팩토링

    • 경험 필터, 검색, 페이지네이션 상태를 URL 검색 파라미터 기반으로 전환하여 상태 공유 및 북마크 기능 개선

@hummingbbird hummingbbird self-assigned this Mar 10, 2026
@hummingbbird hummingbbird linked an issue Mar 10, 2026 that may be closed by this pull request
2 tasks
@github-actions github-actions bot added 🔗API api 연동 🛠️REFACTOR 코드 리팩토링 채영🥦 labels Mar 10, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

경험(Experience) 유형 필터링의 타입 시스템을 강타입 유니온(ExperienceTypeCode)에서 문자열 기반으로 변경하고, 세 개의 페이지 컴포넌트에서 로컬 상태 관리를 React Router의 URL 검색 파라미터 기반으로 마이그레이션합니다.

Changes

Cohort / File(s) Summary
Type System Updates
src/features/experience-detail/types/experience-detail.types.ts, src/shared/api/generate/http-client.ts
ExperienceUpsertBody.typeExperienceRequestDto.type 필드를 특정 문자열 리터럴 유니온에서 일반 문자열 타입으로 변경. 컴파일 타임 타입 안전성 완화.
Component Type Adaptations
src/features/experience-detail/ui/experience-viewer/experience-viewer.tsx, src/widgets/experience-filter/experience-filter.tsx
ExperienceFilterPropsvalueonChange 타입을 문자열 기반으로 업데이트. EXPERIENCE_TYPE 인덱싱 시 타입 캐스팅 추가하여 안전성 확보.
API Query Updates
src/features/experience/api/use-experience-list.query.ts
getExperienceListuseGetExperienceListtype 파라미터를 문자열 기반으로 변경. ExperienceTypeCode 임포트 및 사용 제거.
Page State Management Migration
src/pages/experience/experience-page.tsx, src/pages/home/search-section/search-section.tsx, src/pages/matching-list/matching-list-page.tsx
로컬 상태(useState)에서 useSearchParams로 마이그레이션. 필터/검색 입력을 URL 쿼리 파라미터로 관리. 데이터 페칭 시 URL 파라미터 값 사용. handlePageChange, handleSearch 등의 핸들러가 URL 파라미터 업데이트 수행.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Page as Page Component
    participant Router as React Router
    participant API as API Query
    participant Data as Data Fetching

    User->>Page: 페이지 방문 or 필터 변경
    Page->>Router: useSearchParams() 호출
    Router-->>Page: URL 검색 파라미터 읽기
    Page->>Page: type/page 파라미터 추출
    
    alt 파라미터 변경
        User->>Page: 필터/검색 입력
        Page->>Router: updateSearchParams() 호출
        Router->>Router: URL 업데이트
        Note over Router: ?type=PROJECT&page=1
    end
    
    Page->>API: useGetExperienceList(type, page)
    API->>Data: 서버 요청 (문자열 type)
    Data-->>API: 데이터 반환
    API-->>Page: 결과 데이터
    Page->>User: UI 렌더링
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

🌟FEAT, 채영🥦, 🔗API

Suggested reviewers

  • qowjdals23
  • u-zzn
  • odukong
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 정확히 반영합니다. 페이지네이션 상태를 searchParams를 통해 유지하는 리팩토링이 핵심 목표이며, 제목이 이를 명확히 전달합니다.
Description check ✅ Passed PR 설명이 template 형식을 따르고 있으며, Summary, Tasks, To Reviewer 섹션이 충실하게 작성되어 있습니다. 구체적인 코드 예시와 변경 이유를 설명하고 있습니다.
Linked Issues check ✅ Passed PR이 #152의 요구사항(페이지네이션 상태 유지)을 충분히 충족합니다. useSearchParams 구현으로 page, filter, keyword 상태를 URL에 저장하여 상세 페이지에서 돌아올 때 상태 복원이 가능합니다.
Out of Scope Changes check ✅ Passed 타입 변경(ExperienceTypeCode → string)은 페이지네이션 상태 유지 구현 과정에서 필요한 범위 확장입니다. PR 설명에서 타입 결합도 감소 필요성을 명시하고 있으며, 제한적 범위 내의 합리적인 변경입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

🚀 빌드 결과

린트 검사 완료
빌드 성공

로그 확인하기
Actions 탭에서 자세히 보기

Copy link

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/experience/api/use-experience-list.query.ts (1)

29-32: ⚠️ Potential issue | 🟠 Major

queryKeyqueryFn에서 falsy 값 처리가 불일치해요.

queryKey: experienceQueryKey.list(type ?? "", page),  // "" 으로 정규화
queryFn: () => getExperienceList({ type: type ?? undefined, page }),  // undefined로 정규화

이로 인해 캐시 동작에 문제가 발생할 수 있어요:

  • type=null, type="", type=undefined가 모두 같은 캐시 키("")를 공유하지만
  • 실제 API 호출은 다르게 동작해요 ("" vs undefined 쿼리 파라미터)

캐시 키와 API 호출이 일관되게 동작하도록 통일해주세요.

🔧 일관된 처리를 위한 제안
 export const useGetExperienceList = ({
   type,
   page,
 }: {
   type?: string | null;
   page: number;
 }) => {
+  const normalizedType = type || undefined;
   return useQuery({
-    queryKey: experienceQueryKey.list(type ?? "", page),
-    queryFn: () => getExperienceList({ type: type ?? undefined, page }),
+    queryKey: experienceQueryKey.list(normalizedType ?? "", page),
+    queryFn: () => getExperienceList({ type: normalizedType, page }),
   });
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/experience/api/use-experience-list.query.ts` around lines 29 -
32, Normalize the `type` value the same way in both the cache key and the API
call: choose a single canonical form (e.g., undefined) and apply it to both
`experienceQueryKey.list(...)` and the call to `getExperienceList(...)` so the
cache key matches the actual request parameters; specifically, update the
`queryKey` invocation (experienceQueryKey.list) to accept the same normalized
`type` as the `queryFn` that calls `getExperienceList` (both using type ??
undefined or both using type ?? ""), ensuring consistency between the cache key
and the API request.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/experience/experience-page.tsx`:
- Around line 34-39: handlePageChange currently uses an unnecessary nullish
coalescing (type ?? "") even though type is already derived from
searchParams.get("type") || "" and thus always a string; update the
handlePageChange function to pass type directly to setSearchParams (remove the
"?? \"\"" usage) so setSearchParams({ type: type, page: String(page) }) uses the
existing type variable from the searchParams.get("type") || "" initialization.

In `@src/widgets/experience-filter/experience-filter.tsx`:
- Line 22: Guard the cast of value to ExperienceTypeCode before indexing
EXPERIENCE_TYPE: replace the unvalidated cast in the experience-filter component
by checking membership first (e.g., if (value &&
Object.prototype.hasOwnProperty.call(EXPERIENCE_TYPE, value)) triggerLabel =
EXPERIENCE_TYPE[value as ExperienceTypeCode];) so that triggerLabel cannot
become undefined when an invalid string is passed; reference the symbols value,
EXPERIENCE_TYPE, ExperienceTypeCode, and triggerLabel in your change.

---

Outside diff comments:
In `@src/features/experience/api/use-experience-list.query.ts`:
- Around line 29-32: Normalize the `type` value the same way in both the cache
key and the API call: choose a single canonical form (e.g., undefined) and apply
it to both `experienceQueryKey.list(...)` and the call to
`getExperienceList(...)` so the cache key matches the actual request parameters;
specifically, update the `queryKey` invocation (experienceQueryKey.list) to
accept the same normalized `type` as the `queryFn` that calls
`getExperienceList` (both using type ?? undefined or both using type ?? ""),
ensuring consistency between the cache key and the API request.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cdcf287-49f4-4da2-a5be-0ec5d6d95d1c

📥 Commits

Reviewing files that changed from the base of the PR and between cf791e2 and f513221.

📒 Files selected for processing (8)
  • src/features/experience-detail/types/experience-detail.types.ts
  • src/features/experience-detail/ui/experience-viewer/experience-viewer.tsx
  • src/features/experience/api/use-experience-list.query.ts
  • src/pages/experience/experience-page.tsx
  • src/pages/home/search-section/search-section.tsx
  • src/pages/matching-list/matching-list-page.tsx
  • src/shared/api/generate/http-client.ts
  • src/widgets/experience-filter/experience-filter.tsx

}: ExperienceFilterProps) => {
let triggerLabel = "경험 유형";
if (value) triggerLabel = EXPERIENCE_TYPE[value];
if (value) triggerLabel = EXPERIENCE_TYPE[value as ExperienceTypeCode];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

value가 유효한 ExperienceTypeCode인지 검증 없이 캐스팅해요.

experience-viewer.tsx에서는 value in EXPERIENCE_TYPE 검사를 수행한 후 캐스팅하지만, 여기서는 검증 없이 바로 캐스팅하고 있어요. 유효하지 않은 문자열이 전달되면 triggerLabelundefined가 될 수 있어요.

🛡️ 방어적 검사 추가 제안
 let triggerLabel = "경험 유형";
-if (value) triggerLabel = EXPERIENCE_TYPE[value as ExperienceTypeCode];
+if (value && value in EXPERIENCE_TYPE) {
+  triggerLabel = EXPERIENCE_TYPE[value as ExperienceTypeCode];
+}
 else if (isTouched && hasTotal) triggerLabel = "전체";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (value) triggerLabel = EXPERIENCE_TYPE[value as ExperienceTypeCode];
let triggerLabel = "경험 유형";
if (value && value in EXPERIENCE_TYPE) {
triggerLabel = EXPERIENCE_TYPE[value as ExperienceTypeCode];
}
else if (isTouched && hasTotal) triggerLabel = "전체";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets/experience-filter/experience-filter.tsx` at line 22, Guard the
cast of value to ExperienceTypeCode before indexing EXPERIENCE_TYPE: replace the
unvalidated cast in the experience-filter component by checking membership first
(e.g., if (value && Object.prototype.hasOwnProperty.call(EXPERIENCE_TYPE,
value)) triggerLabel = EXPERIENCE_TYPE[value as ExperienceTypeCode];) so that
triggerLabel cannot become undefined when an invalid string is passed; reference
the symbols value, EXPERIENCE_TYPE, ExperienceTypeCode, and triggerLabel in your
change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

채영🥦 🔗API api 연동 🛠️REFACTOR 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 페이지네이션 상태 유지되도록 개선

1 participant