-
Notifications
You must be signed in to change notification settings - Fork 4
[Refactor] pagination 상태 유지 개선(searchParams) #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
35d3c30
8c43f12
bb02dae
f513221
8a8d1cd
8f4199e
73cfe54
c84f628
9793c39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { useState } from "react"; | ||
| import { useNavigate } from "react-router-dom"; | ||
| import { useSearchParams, useNavigate } from "react-router-dom"; | ||
|
|
||
| import { ROUTES } from "@/app/routes/paths"; | ||
| import { useGetExperienceList } from "@/features/experience/api/use-experience-list.query"; | ||
|
|
@@ -9,24 +9,33 @@ import { ExperienceFilter } from "@/widgets"; | |
| import * as styles from "./experience-page.css"; | ||
| import { ExperienceListContainer } from "./ui/experience-list-container"; | ||
|
|
||
| import type { ExperienceTypeCode } from "@/shared/config/experience"; | ||
|
|
||
| const ExperiencePage = () => { | ||
| const [filter, setFilter] = useState<ExperienceTypeCode | null>(null); | ||
| const navigate = useNavigate(); | ||
| const [searchParams, setSearchParams] = useSearchParams(); | ||
|
|
||
| const currentPage = Number(searchParams.get("page")) || 1; | ||
|
hummingbbird marked this conversation as resolved.
Outdated
|
||
| const type = searchParams.get("type") || ""; | ||
|
hummingbbird marked this conversation as resolved.
Outdated
|
||
|
|
||
| const [isExpTouched, setIsExpTouched] = useState(false); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사소한 부분인데요..!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말씀주신 대로 현재는 isExpTouched가 state이기 때문에, 페이지 리렌더링 시 false로 초기화 되고 있습니다. 하지만 현재 로직상 type 파라미터가 비어있을 때 '전체' 데이터를 보여주고 있어서 필터 라벨이 기본값(경험 유형)으로 돌아가더라도 UX 측면에서 큰 지장이 없다고 생각해 지금과 같은 방식으로 구현했습니다 😀 허나 ‘전체’와 ‘경험 유형’은 엄연히 다른 상태이기 때문에 구분하는 것이 더 좋을 것이라고 생각해요! 우선 우선순위가 더 높은 쿼리스트링값의 유지를 우선적으로 구현하고 해당 부분은 추후 스프린트에 적용할까 하는데 정민님 생각은 어떠신가요??
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 이해했습니다! 말씀해주신 것처럼 현재 PR에서는 쿼리스트링 기반으로 목록 상태를 유지하는 쪽이 우선순위가 더 높아 보여서 ! 지금 방향으로 가도 괜찮을 것 같아요 👍 전체 / 경험 유형 구분 부분은 추후 스프린트에서 보완되면 더 좋을 것 같습니다!! 설명 감사합니다 😄 |
||
| const [currentPage, setCurrentPage] = useState(1); | ||
| const navigate = useNavigate(); | ||
|
|
||
| const { data } = useGetExperienceList({ | ||
| type: filter, | ||
| type, | ||
| page: currentPage, | ||
| }); | ||
|
|
||
| const handleFilterChange = (value: ExperienceTypeCode | null) => { | ||
| const handleFilterChange = (value: string) => { | ||
| setIsExpTouched(true); | ||
| setFilter(value); | ||
| setCurrentPage(1); | ||
| setSearchParams({ | ||
| type: value, | ||
| page: "1", | ||
| }); | ||
| }; | ||
|
|
||
| const handlePageChange = (page: number) => { | ||
| setSearchParams({ | ||
| type: type ?? "", | ||
| page: String(page), | ||
| }); | ||
| }; | ||
|
hummingbbird marked this conversation as resolved.
|
||
|
|
||
| return ( | ||
|
|
@@ -53,15 +62,15 @@ const ExperiencePage = () => { | |
| </button> | ||
|
|
||
| <ExperienceFilter | ||
| value={filter} | ||
| value={type} | ||
| onChange={handleFilterChange} | ||
| isTouched={isExpTouched} | ||
| hasTotal={true} | ||
| /> | ||
| </div> | ||
| </section> | ||
|
|
||
| <ExperienceListContainer data={data} onPageChange={setCurrentPage} /> | ||
| <ExperienceListContainer data={data} onPageChange={handlePageChange} /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { useState } from "react"; | ||
| import { useState, useEffect } from "react"; | ||
| import { useSearchParams } from "react-router-dom"; | ||
|
|
||
| import { useGetCompanies } from "@/features/home"; | ||
| import { ScaleFilter, IndustryFilter } from "@/features/home/ui"; | ||
|
|
@@ -11,42 +12,66 @@ import * as styles from "./search-section.css"; | |
|
|
||
| import type { IndustryCode, ScaleCode } from "@/shared/config"; | ||
|
|
||
| interface CompanySearchParamsType { | ||
| keyword?: string; | ||
| industry?: IndustryCode; | ||
| scale?: ScaleCode; | ||
| sort?: string; | ||
| page?: number; | ||
| isRecruited?: boolean; | ||
| } | ||
|
|
||
| const SearchSection = () => { | ||
| const [params, setParams] = useState<CompanySearchParamsType>({ | ||
| page: 1, | ||
| isRecruited: true, | ||
| }); | ||
| const [searchParams, setSearchParams] = useSearchParams(); | ||
|
|
||
| const keyword = searchParams.get("keyword") || ""; | ||
| const industry = (searchParams.get("industry") as IndustryCode) || undefined; | ||
| const scale = (searchParams.get("scale") as ScaleCode) || undefined; | ||
|
hummingbbird marked this conversation as resolved.
Outdated
|
||
| const page = Number(searchParams.get("page")) || 1; | ||
| const isRecruited = searchParams.get("isRecruited") !== "false"; | ||
|
|
||
| const params = { | ||
| keyword, | ||
| industry, | ||
| scale, | ||
| page, | ||
| isRecruited, | ||
| }; | ||
|
|
||
| const { data, isLoading, isPlaceholderData } = useGetCompanies(params); | ||
| const content = data?.content || []; | ||
| const hasResult = content.length > 0; | ||
| const [searchValue, setSearchValue] = useState(""); | ||
| const currentPage = params.page ?? 1; | ||
|
|
||
| const [searchValue, setSearchValue] = useState(keyword); | ||
|
|
||
| const [isScaleTouched, setIsScaleTouched] = useState(false); | ||
| const [isIndustryTouched, setIsIndustryTouched] = useState(false); | ||
|
|
||
| const updateParams = (patch: Partial<CompanySearchParamsType>) => { | ||
| setParams((prev) => ({ | ||
| ...prev, | ||
| ...patch, | ||
| })); | ||
| const updateSearchParams = ( | ||
| patch: Record<string, string | number | boolean | undefined> | ||
| ) => { | ||
| const newParams = new URLSearchParams(searchParams); | ||
|
|
||
| Object.entries(patch).forEach(([key, value]) => { | ||
| if (value === undefined || value === "") { | ||
| newParams.delete(key); | ||
| } else { | ||
| newParams.set(key, String(value)); | ||
| } | ||
| }); | ||
|
|
||
| if (!patch.page) { | ||
| newParams.set("page", "1"); | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 패치된 값 기준으로 page를 자동으로
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 지적 감사합니다! 유진님 말씀대로 조건문의 목적을 고려했을 때 제안해주신 방향으로 수정하는 게 더 적합한 것 같습니다! 반영하겠습니다 😀 |
||
|
|
||
| setSearchParams(newParams); | ||
| }; | ||
|
|
||
| const handlePageChange = (newPage: number) => { | ||
| if (isPlaceholderData) return; | ||
| updateParams({ page: newPage }); | ||
| updateSearchParams({ page: newPage }); | ||
| }; | ||
|
|
||
| const handleSearch = (newKeyword: string) => { | ||
| updateSearchParams({ keyword: newKeyword }); | ||
| }; | ||
|
|
||
| // 검색값 유지 | ||
| useEffect(() => { | ||
| setSearchValue(keyword); | ||
| }, [keyword]); | ||
|
|
||
| return ( | ||
| <> | ||
| <section | ||
|
|
@@ -68,7 +93,7 @@ const SearchSection = () => { | |
| placeholder="지원하고 싶은 기업을 검색해보세요" | ||
| value={searchValue} | ||
| onChange={setSearchValue} | ||
| onSearch={(keyword) => updateParams({ keyword, page: 1 })} | ||
| onSearch={handleSearch} | ||
| /> | ||
| </div> | ||
| </div> | ||
|
|
@@ -78,20 +103,20 @@ const SearchSection = () => { | |
| <div className={styles.container}> | ||
| <div className={styles.filterWrapper}> | ||
| <IndustryFilter | ||
| value={params.industry ?? null} | ||
| value={industry ?? null} | ||
| isTouched={isIndustryTouched} | ||
| onChange={(industry) => { | ||
| onChange={(newIndustry) => { | ||
| setIsIndustryTouched(true); | ||
| updateParams({ industry, page: 1 }); | ||
| updateSearchParams({ industry: newIndustry }); | ||
| }} | ||
| /> | ||
|
|
||
| <ScaleFilter | ||
| value={params.scale} | ||
| value={scale} | ||
| isTouched={isScaleTouched} | ||
| onChange={(scale) => { | ||
| onChange={(newScale) => { | ||
| setIsScaleTouched(true); | ||
| updateParams({ scale, page: 1 }); | ||
| updateSearchParams({ scale: newScale }); | ||
| }} | ||
| /> | ||
|
|
||
|
|
@@ -100,9 +125,9 @@ const SearchSection = () => { | |
| </p> | ||
|
|
||
| <Toggle | ||
| checked={params.isRecruited ?? true} | ||
| onCheckedChange={(isRecruited) => | ||
| updateParams({ isRecruited, page: 1 }) | ||
| checked={isRecruited} | ||
| onCheckedChange={(checked) => | ||
| updateSearchParams({ isRecruited: checked }) | ||
| } | ||
| /> | ||
| </div> | ||
|
|
@@ -122,7 +147,7 @@ const SearchSection = () => { | |
| ))} | ||
| </div> | ||
| <Pagination | ||
| currentPage={currentPage} | ||
| currentPage={page} | ||
| totalPage={data?.totalPage ?? 1} | ||
| onPageChange={handlePageChange} | ||
| /> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.