fix: 문의글 페이지네이션 수정#355
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughServer-side pagination was introduced in InquiryTabContent by passing page/size to useInquiryList and using API-provided totalPages. Client-side slicing was removed; filtering/sorting remains per page. The hook’s implementation style changed to an explicit return without altering behavior. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant C as InquiryTabContent
participant H as useInquiryList (React Query)
participant S as Inquiry API
U->>C: Change page/filter/sort
rect rgba(200,230,255,0.3)
note right of C: Server-side pagination
C->>H: useInquiryList(page-1, size)
H->>S: GET /inquiries?page={page-1}&size={size}
S-->>H: { content, totalPages, ... }
H-->>C: data
end
C->>C: Apply client-side filter/sort on current page data
C-->>U: Render table + pagination (uses totalPages)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
📝 추가 및 변경된 파일총 2개 파일 변경 |
✅ Mosu Admin 빌드가 성공적으로 완료되었습니다!📋 빌드 결과
🚀 다음 단계코드 리뷰 후 머지하면 프로덕션에 배포됩니다. |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
mosu-admin/src/components/inquiry/InquiryTabContent.tsx (2)
52-55: Clamp page bounds and scroll to top for better UX; consider memoizing the handler.Avoid out-of-range page values and auto-scroll to keep context after page change. Also,
useCallbackreduces re-renders ofInquiryPagination.-const handlePageChange = (page: number) => { - setCurrentPage(page); -}; +const handlePageChange = useCallback((page: number) => { + const next = Math.min(Math.max(page, 1), totalPages || 1); + if (next !== currentPage) { + setCurrentPage(next); + window.scrollTo({ top: 0, behavior: "smooth" }); + } +}, [currentPage, totalPages]);Add
useCallbackimport at the top:import { useState, useMemo, useCallback } from "react";
81-81: Per-page client sorting can contradict server order across pages.You’re re-sorting only the current page’s 10 items. Users may see the first page sorted “최신순”, then page 2 not following that global order. Consider:
- Delegating sorting to the API (preferred; see Lines 16–18 comment), or
- Removing client-side sorting when server paging is active to preserve the server’s stable order, or
- If you must keep it, reflect that sorting applies per-page in the UI.
Also, if “최신순” truly means recency, prefer
createdAtoveridunlessidis guaranteed to be strictly time-ordered.
📜 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.
📒 Files selected for processing (2)
mosu-admin/src/api/inquiry/getInquiryList.ts(1 hunks)mosu-admin/src/components/inquiry/InquiryTabContent.tsx(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
mosu-admin/src/components/inquiry/InquiryTabContent.tsx (3)
mosu-admin/src/api/inquiry/getInquiryList.ts (1)
useInquiryList(63-68)mosu-admin/src/components/inquiry/InquiryTable.tsx (1)
InquiryTable(12-85)mosu-admin/src/components/inquiry/InquiryPagination.tsx (1)
InquiryPagination(11-87)
| export const useInquiryList = (page: number = 0, size: number = 10) => { | ||
| return useSuspenseQuery({ | ||
| queryKey: ["inquiry", page, size], | ||
| queryFn: () => getInquiryList(page, size), | ||
| }); | ||
| }; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Align the hook with the component’s loading/error handling (switch to useQuery or adopt Suspense/ErrorBoundary).
useInquiryList uses useSuspenseQuery, while the caller (InquiryTabContent) branches on isLoading/error. With Suspense-enabled queries, the component typically never hits those branches; instead it should be wrapped by <Suspense fallback=...> and an ErrorBoundary. Two viable paths:
- Path A (non-Suspense, keeps current UI): switch to
useQuery, keepisLoading/error, and addplaceholderData: keepPreviousDatato smooth page transitions. - Path B (Suspense): keep
useSuspenseQuery, remove localisLoading/errorchecks, and render via<Suspense>+ ErrorBoundary.
Suggested (Path A) minimal diff for this hook:
-export const useInquiryList = (page: number = 0, size: number = 10) => {
- return useSuspenseQuery({
- queryKey: ["inquiry", page, size],
- queryFn: () => getInquiryList(page, size),
- });
-};
+export const useInquiryList = (page: number = 0, size: number = 10) => {
+ return useQuery({
+ queryKey: ["inquiry", page, size],
+ queryFn: () => getInquiryList(page, size),
+ // smooth UX while switching pages
+ placeholderData: keepPreviousData,
+ staleTime: 5_000,
+ });
+};Outside this hunk, update imports at the top of the file:
// replace useSuspenseQuery import
import { useQuery, keepPreviousData } from "@tanstack/react-query";Do you want me to push the complementary change in InquiryTabContent to keep or drop the manual loaders depending on your chosen path?
🏁 Script executed:
# Print import section of getInquiryList.ts
sed -n '1,50p' mosu-admin/src/api/inquiry/getInquiryList.ts
# Find component file containing InquiryTabContent
rg -nP 'InquiryTabContent' -g '*.ts*'Length of output: 1601
🏁 Script executed:
#!/bin/bash
# Display usage of useInquiryList in InquiryTabContent
rg -nP --context 5 'useInquiryList' -g 'mosu-admin/src/components/inquiry/InquiryTabContent.tsx'Length of output: 1977
🏁 Script executed:
#!/bin/bash
# Show first 100 lines of InquiryTabContent to inspect loading/error handling
sed -n '1,100p' mosu-admin/src/components/inquiry/InquiryTabContent.tsxLength of output: 2893
Align your data-fetch hook and component to the same loading/error strategy
The useInquiryList hook currently uses useSuspenseQuery, which only returns a guaranteed data property and relies on React Suspense and error boundaries for loading and error states—there is no isLoading, isError, or placeholderData available from this hook (tanstack.com). Meanwhile, InquiryTabContent destructures { data, isLoading, error } and branches on those flags, so it will never hit its local loaders or error UI when using Suspense mode.
You have two clear options—choose one path and align both hook and component accordingly:
-
Path A: Non-Suspense (keep local loaders)
• Change the hook to useuseQueryinstead ofuseSuspenseQuery
• AddplaceholderData: keepPreviousDataand a shortstaleTimefor smoother page transitions
• Update imports to pulluseQueryandkeepPreviousDatafrom React Query
• Leave the existingisLoading/errorchecks inInquiryTabContent--- mosu-admin/src/api/inquiry/getInquiryList.ts @@ -import { useSuspenseQuery } from "@tanstack/react-query"; +import { useQuery, keepPreviousData } from "@tanstack/react-query"; export const useInquiryList = (page: number = 0, size: number = 10) => { - return useSuspenseQuery({ - queryKey: ["inquiry", page, size], - queryFn: () => getInquiryList(page, size), - }); + return useQuery({ + queryKey: ["inquiry", page, size], + queryFn: () => getInquiryList(page, size), + // smooth UX while paging + placeholderData: keepPreviousData, + staleTime: 5_000, + }); };
-
Path B: Suspense (lift loading/error to boundaries)
• KeepuseSuspenseQueryin the hook
• Remove theisLoadinganderrorbranches inInquiryTabContent
• Wrap<InquiryTabContent />in a<Suspense fallback={...}>and an<ErrorBoundary>at a higher level
• Focus the component on its “happy path” (renderingdataonly)
Please pick one approach and update both the hook and its consumer. Let me know if you’d like me to push the complementary changes in InquiryTabContent once you decide.
📝 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.
| export const useInquiryList = (page: number = 0, size: number = 10) => { | |
| return useSuspenseQuery({ | |
| queryKey: ["inquiry", page, size], | |
| queryFn: () => getInquiryList(page, size), | |
| }); | |
| }; | |
| // mosu-admin/src/api/inquiry/getInquiryList.ts | |
| import { useQuery, keepPreviousData } from "@tanstack/react-query"; | |
| export const useInquiryList = (page: number = 0, size: number = 10) => { | |
| - return useSuspenseQuery({ | |
| - queryKey: ["inquiry", page, size], | |
| - queryFn: () => getInquiryList(page, size), | |
| return useQuery({ | |
| queryKey: ["inquiry", page, size], | |
| queryFn: () => getInquiryList(page, size), | |
| // smooth UX while paging | |
| placeholderData: keepPreviousData, | |
| staleTime: 5_000, | |
| }); | |
| }; |
🤖 Prompt for AI Agents
In mosu-admin/src/api/inquiry/getInquiryList.ts around lines 63 to 68, change
the hook from useSuspenseQuery to a non-suspense useQuery so the consumer's
local loaders/error handling works: import useQuery and keepPreviousData from
react-query, call useQuery({ queryKey: ["inquiry", page, size], queryFn: () =>
getInquiryList(page, size), placeholderData: keepPreviousData(), staleTime: 5000
}) (or similar short staleTime), and leave InquiryTabContent's existing
isLoading/error branches as-is.
| const { data, isLoading, error } = useInquiryList(currentPage - 1, itemsPerPage); | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Potential runtime/design mismatch: Suspense query used with manual isLoading/error checks.
Given the current useInquiryList implementation uses useSuspenseQuery (unless you adopt the hook change I suggested), this component’s isLoading/error branches won’t execute as intended. Choose one:
- Keep manual loaders here → switch the hook to
useQuery(recommended minimal change). - Or keep
useSuspenseQuery→ remove these branches and rely on<Suspense fallback="로딩 중..." />and an ErrorBoundary higher up.
If you keep Suspense, this block can be simplified to only destructure data:
-const { data, isLoading, error } = useInquiryList(currentPage - 1, itemsPerPage);
+const { data } = useInquiryList(currentPage - 1, itemsPerPage);If you switch the hook to useQuery, no change needed here.
To quickly verify whether a Suspense/ErrorBoundary exists around this component:
🏁 Script executed:
#!/bin/bash
# Search for Suspense and ErrorBoundary usage near InquiryTabContent mounts
rg -nP -C3 '<Suspense\b|ErrorBoundary' --type=tsx
# Where is InquiryTabContent used?
rg -nP 'InquiryTabContent' --type=tsx -C3Length of output: 149
🏁 Script executed:
#!/bin/bash
# Retry searching for Suspense and ErrorBoundary without relying on file-type filters
rg -nP '<Suspense\b|ErrorBoundary' -g '*.tsx' -C3
# Locate where InquiryTabContent is imported and used
rg -nP 'InquiryTabContent' -g '*.tsx' -C3Length of output: 3678
🏁 Script executed:
#!/bin/bash
# Locate the implementation of useInquiryList to determine if it uses useSuspenseQuery or useQuery
rg -nP 'export function useInquiryList' -g '*.ts*' -C3 || rg -nP 'export const useInquiryList' -g '*.ts*' -C3Length of output: 1252
Require a consistent loading strategy for InquiryTabContent
It looks like in mosu-admin, useInquiryList is backed by useSuspenseQuery, so the component will suspend (throw a promise) during loading and throw an error on failure—meaning the isLoading/error checks in InquiryTabContent.tsx will never run, and without a <Suspense> boundary to catch the suspended state, the app will error at runtime.
Choose one of the following fixes:
• Switch to a manual loader
- In
mosu-admin/src/api/inquiry/getInquiryList.ts, replaceuseSuspenseQuerywithuseQueryso your existingisLoading/errorbranches work as intended.
• Adopt Suspense fully
- Wrap the component (or a parent, such as in
InquiryBoardorRouterProviderinApp.tsx) in<Suspense fallback="로딩 중..." />. - In
InquiryTabContent.tsx, remove theisLoading/errordestructuring and branches, simplifying to:- const { data, isLoading, error } = useInquiryList(currentPage - 1, itemsPerPage); + const { data } = useInquiryList(currentPage - 1, itemsPerPage);
Locations to update:
- api/inquiry/getInquiryList.ts
- components/inquiry/InquiryTabContent.tsx
- (optionally) wrap in
SuspensewithinInquiryBoard.tsxorApp.tsxif using Suspense.
📝 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.
| const { data, isLoading, error } = useInquiryList(currentPage - 1, itemsPerPage); | |
| const { data } = useInquiryList(currentPage - 1, itemsPerPage); |
🤖 Prompt for AI Agents
In mosu-admin/src/components/inquiry/InquiryTabContent.tsx around lines 14-15,
the component is using a suspense-backed hook so its local isLoading/error
branches never run and will crash without a Suspense boundary; either (A) change
the hook to a manual loader by updating
mosu-admin/src/api/inquiry/getInquiryList.ts to use useQuery instead of
useSuspenseQuery so the component can keep its isLoading/error checks, or (B)
adopt Suspense fully by wrapping this component (or a parent such as
InquiryBoard or App's RouterProvider) in <Suspense fallback="로딩 중..." /> and
remove the isLoading/error destructuring and conditional branches from
InquiryTabContent.tsx; update the listed files accordingly.
| const inquiryData = useMemo(() => data?.content || [], [data?.content]); | ||
| const totalPages = data?.totalPages || 0; | ||
|
|
There was a problem hiding this comment.
Server pages + client-only filters/sort will yield empty/confusing pages. Push filters/sort to the API or hide pagination while filtered.
You’re driving pagination with data.totalPages from the server but applying filters/sort only on the current page’s content. This causes:
- Pages with 0 rows after filtering (common).
- Pagination count reflecting the unfiltered dataset, not the filtered subset.
Preferred fix: implement server-side filtering/sorting and include them in the request and the query key so totalPages aligns with the filtered dataset. Example call shape:
-const { data, isLoading, error } = useInquiryList(currentPage - 1, itemsPerPage);
+const serverStatuses =
+ selectedFilters.includes("전체") ? undefined : selectedFilters; // e.g., ["미응답","완료"]
+const serverSort =
+ sortBy === "최신순" ? "CREATED_AT_DESC" :
+ sortBy === "오래된순" ? "CREATED_AT_ASC" :
+ sortBy === "제목순" ? "TITLE_ASC" : undefined;
+const { data, isLoading, error } = useInquiryList(
+ currentPage - 1,
+ itemsPerPage,
+ { statuses: serverStatuses, sort: serverSort }
+);And adjust the hook (same hunk as in the API file) to accept opts and use it in the query key and request params:
-export const useInquiryList = (page: number = 0, size: number = 10) => {
- return useQuery({
- queryKey: ["inquiry", page, size],
- queryFn: () => getInquiryList(page, size),
- placeholderData: keepPreviousData,
- staleTime: 5_000,
- });
-};
+export const useInquiryList = (
+ page: number = 0,
+ size: number = 10,
+ opts?: { statuses?: string[]; sort?: "CREATED_AT_DESC"|"CREATED_AT_ASC"|"TITLE_ASC"|"TITLE_DESC" }
+) => {
+ return useQuery({
+ queryKey: ["inquiry", page, size, opts],
+ queryFn: () => getInquiryList(page, size, opts),
+ placeholderData: keepPreviousData,
+ staleTime: 5_000,
+ });
+};API call adjustment (outside this file, in getInquiryList.ts):
// extend signature
export const getInquiryList = async (
page: number = 0,
size: number = 10,
opts?: { statuses?: string[]; sort?: string }
): Promise<InquiryListResponse> => {
const params = {
page,
size,
...(opts?.statuses ? { statuses: opts.statuses.join(",") } : {}),
...(opts?.sort ? { sort: opts.sort } : {}),
};
const { data: response } = await api.get<BaseResponse<InquiryListResponse>>(
"/admin/inquiry/list",
{ params }
);
return response.data;
};Short-term mitigation (if backend changes aren’t ready): hide pagination when any filter other than “전체” is active to avoid misleading navigation (diff provided on Lines 83–87 below).
| <InquiryPagination | ||
| currentPage={currentPage} | ||
| totalPages={totalPages} | ||
| onPageChange={setCurrentPage} | ||
| onPageChange={handlePageChange} | ||
| /> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Hide pagination when client-only filters are active (mitigation until server-side filtering lands).
This avoids empty/misleading pages when a filter is applied but the server still paginates the unfiltered dataset.
- <InquiryPagination
- currentPage={currentPage}
- totalPages={totalPages}
- onPageChange={handlePageChange}
- />
+ {selectedFilters.includes("전체") && (
+ <InquiryPagination
+ currentPage={currentPage}
+ totalPages={totalPages}
+ onPageChange={handlePageChange}
+ />
+ )}🏁 Script executed:
#!/bin/bash
# Search for selectedFilters usage around pagination in the InquiryTabContent component
rg -n 'selectedFilters' -C 5 mosu-admin/src/components/inquiry/InquiryTabContent.tsxLength of output: 1680
🏁 Script executed:
# Show the code around the pagination component in InquiryTabContent
sed -n '70,100p' mosu-admin/src/components/inquiry/InquiryTabContent.tsxLength of output: 586
🏁 Script executed:
#!/bin/bash
# Locate the type definitions for FilterOption to confirm available filter values
find mosu-admin/src/components/inquiry -type f -name "types.ts"Length of output: 106
🏁 Script executed:
#!/bin/bash
# Display FilterOption definitions to confirm available filter values
sed -n '1,200p' mosu-admin/src/components/inquiry/types.tsLength of output: 381
🏁 Script executed:
#!/bin/bash
# Locate handlePageChange definition to confirm its behavior
rg -n 'handlePageChange' -C3 mosu-admin/src/components/inquiry/InquiryTabContent.tsxLength of output: 446
Hide pagination when client-side filters are active
To prevent displaying pagination controls on filtered datasets (which can lead to empty or misleading pages), wrap the <InquiryPagination> render in a check for the default “전체” filter:
• File: mosu-admin/src/components/inquiry/InquiryTabContent.tsx
• Around lines 83–87
- <InquiryPagination
- currentPage={currentPage}
- totalPages={totalPages}
- onPageChange={handlePageChange}
- />
+ {selectedFilters.includes("전체") && (
+ <InquiryPagination
+ currentPage={currentPage}
+ totalPages={totalPages}
+ onPageChange={handlePageChange}
+ />
+ )}📝 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.
| <InquiryPagination | |
| currentPage={currentPage} | |
| totalPages={totalPages} | |
| onPageChange={setCurrentPage} | |
| onPageChange={handlePageChange} | |
| /> | |
| {selectedFilters.includes("전체") && ( | |
| <InquiryPagination | |
| currentPage={currentPage} | |
| totalPages={totalPages} | |
| onPageChange={handlePageChange} | |
| /> | |
| )} |
🤖 Prompt for AI Agents
In mosu-admin/src/components/inquiry/InquiryTabContent.tsx around lines 83–87,
the InquiryPagination should be hidden when client-side filters are active;
modify the JSX to only render <InquiryPagination .../> when the active filter is
the default "전체" (e.g., check selectedFilter === '전체' or equivalent prop/state),
or more robustly when no client-side filters/search terms are set (ensure you
reference the existing filter state variable names in this component). Wrap the
component in that conditional so pagination is not shown for filtered datasets.
✅ Linked Issue
🔍 What I did
Summary by CodeRabbit
New Features
Refactor