-
Notifications
You must be signed in to change notification settings - Fork 1
refactor: 뒤로가기 캐시 무효화 로직 작성 #42
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 all commits
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,6 +1,6 @@ | ||
| 'use client'; | ||
|
|
||
| import { useState, useMemo } from 'react'; | ||
| import { useState, useMemo, useEffect, useCallback } from 'react'; | ||
| import Image from 'next/image'; | ||
| import { useOpenModal } from '@/hooks/useOpenModal'; | ||
| import { useParams, useRouter } from 'next/navigation'; | ||
|
|
@@ -44,10 +44,13 @@ export default function Page() { | |
|
|
||
| const myRoute = routesWithColor.find((route) => route.nickname === myNickname); | ||
| const travelTime = myRoute?.travelTime || 0; | ||
|
|
||
|
|
||
| const totalTravelTime = routesWithColor.reduce((sum, route) => sum + (route.travelTime || 0), 0); | ||
| const averageTravelTime = routesWithColor.length > 0 ? Math.round(totalTravelTime / routesWithColor.length) : 0; | ||
|
|
||
| const totalTravelTime = routesWithColor.reduce( | ||
| (sum, route) => sum + (route.travelTime || 0), | ||
| 0 | ||
| ); | ||
| const averageTravelTime = | ||
| routesWithColor.length > 0 ? Math.round(totalTravelTime / routesWithColor.length) : 0; | ||
|
|
||
| const extractLineNumber = (linenumber: string): string => { | ||
| if (!linenumber) return ''; | ||
|
|
@@ -107,82 +110,36 @@ export default function Page() { | |
| }; | ||
| }); | ||
| }, [midpointData, myNickname, id]); | ||
|
|
||
| // 카테고리 텍스트 생성 함수 | ||
| const getCategoryText = (category: string | undefined): string => { | ||
| if (!category) return '밍글링 추천 1위'; | ||
| return `${category}이 많은 장소`; | ||
|
|
||
| // 카테고리 종성에 따라 "이/가"를 다르게 렌더링 | ||
| const lastChar = category.charCodeAt(category.length - 1); | ||
| const hasJongseong = (lastChar - 0xac00) % 28 !== 0; | ||
| return `${category}${hasJongseong ? '이' : '가'} 많은 장소`; | ||
| }; | ||
|
|
||
| const [selectedResultId, setSelectedResultId] = useState<number>(1); | ||
|
|
||
| const handleModifyStart = () => { | ||
| // 뒤로 가기 클릭 시 캐시 데이터 무효화 | ||
| const clearRelatedCache = useCallback(() => { | ||
| queryClient.removeQueries({ queryKey: ['midpoint', id] }); | ||
| queryClient.removeQueries({ queryKey: ['recommend', id] }); | ||
| router.back(); | ||
| }; | ||
| }, [id, queryClient]); | ||
|
|
||
| const getLineColor = (fullLineName: string) => { | ||
| const cleaned = fullLineName.replace('호선', '').trim(); | ||
| if (/^\d+$/.test(cleaned)) { | ||
| switch (cleaned) { | ||
| case '1': | ||
| return 'bg-[#004A85]'; | ||
| case '2': | ||
| return 'bg-[#00A23F]'; | ||
| case '3': | ||
| return 'bg-[#ED6C00]'; | ||
| case '4': | ||
| return 'bg-[#009BCE]'; | ||
| case '5': | ||
| return 'bg-[#794698]'; | ||
| case '6': | ||
| return 'bg-[#7C4932]'; | ||
| case '7': | ||
| return 'bg-[#6E7E31]'; | ||
| case '8': | ||
| return 'bg-[#D11D70]'; | ||
| case '9': | ||
| return 'bg-[#A49D87]'; | ||
| default: | ||
| return 'bg-gray-400'; | ||
| } | ||
| } | ||
| switch (fullLineName) { | ||
| case '우이신설선': | ||
| return 'bg-[#B0CE18]'; | ||
| case '신림선': | ||
| return 'bg-[#5E7DBB]'; | ||
| case '의정부경전철': | ||
| return 'bg-[#F0831E]'; | ||
| case '용인에버라인': | ||
| return 'bg-[#44A436]'; | ||
| case '인천2호선': | ||
| return 'bg-[#F4A462]'; | ||
| case '김포골드라인': | ||
| return 'bg-[#F4A462]'; | ||
| case '경의선': | ||
| case '경의중앙선': | ||
| return 'bg-[#6AC2B3]'; | ||
| case '수인분당선': | ||
| return 'bg-[#ECA300]'; | ||
| case '신분당선': | ||
| return 'bg-[#B81B30]'; | ||
| case '인천1호선': | ||
| return 'bg-[#B4C7E7]'; | ||
| case '공항철도': | ||
| return 'bg-[#0079AC]'; | ||
| case '경춘선': | ||
| return 'bg-[#007A62]'; | ||
| case '경강산': | ||
| return 'bg-[#0B318F]'; | ||
| case '서해선': | ||
| return 'bg-[#5EAC41]'; | ||
| default: | ||
| return 'bg-gray-400'; | ||
| } | ||
| const handleModifyStart = () => { | ||
| clearRelatedCache(); | ||
| router.replace(`/meeting/${id}`); | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| clearRelatedCache(); | ||
|
|
||
| return () => clearRelatedCache(); | ||
| }, [clearRelatedCache]); | ||
|
Comment on lines
+137
to
+141
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. 마운트 시
뒤로가기 시 캐시 무효화가 목적이라면, 언마운트 시에만 캐시를 제거하면 충분합니다. 마운트 시에는 🔧 수정 제안: 마운트 시 invalidate, 언마운트 시 remove useEffect(() => {
- clearRelatedCache();
-
- return () => clearRelatedCache();
- }, [clearRelatedCache]);
+ // 마운트 시에는 백그라운드 리패치 (기존 데이터 유지)
+ queryClient.invalidateQueries({ queryKey: ['midpoint', id] });
+ queryClient.invalidateQueries({ queryKey: ['recommend', id] });
+
+ // 언마운트 시에는 캐시 완전 제거 (뒤로가기 캐시 무효화)
+ return () => {
+ queryClient.removeQueries({ queryKey: ['midpoint', id] });
+ queryClient.removeQueries({ queryKey: ['recommend', id] });
+ };
+ }, [id, queryClient]);또는 마운트 시 캐시 무효화가 아예 필요 없다면 언마운트 cleanup만 남기세요: useEffect(() => {
return () => clearRelatedCache();
}, [clearRelatedCache]);🤖 Prompt for AI Agents |
||
|
|
||
| return ( | ||
| <div className="flex items-center justify-center p-0 md:min-h-[calc(100vh-200px)] md:py-20"> | ||
| <div className="flex h-full w-full flex-col bg-white md:h-175 md:w-174 md:flex-row md:gap-4 lg:w-215"> | ||
|
|
@@ -196,7 +153,7 @@ export default function Page() { | |
| <div className="px-5 pt-5 md:p-0"> | ||
| <div className="flex items-center justify-between"> | ||
| <button | ||
| onClick={() => router.back()} | ||
| onClick={handleModifyStart} | ||
| className="flex items-center justify-center" | ||
| type="button" | ||
| > | ||
|
|
@@ -248,41 +205,46 @@ export default function Page() { | |
| </div> | ||
| ) : ( | ||
| locationResults.map((result) => { | ||
| const category = meetingData?.data?.purposes?.[meetingData.data.purposes.length - 1]; | ||
| const category = | ||
| meetingData?.data?.purposes?.[meetingData.data.purposes.length - 1]; | ||
| const categoryText = getCategoryText(category); | ||
|
|
||
| const handleRecommendClick = (e: React.MouseEvent) => { | ||
| e.stopPropagation(); | ||
| if (!id || !result.endStation) return; | ||
|
|
||
| let meetingType = ''; | ||
| let categoryParam = ''; | ||
|
|
||
| if (typeof window !== 'undefined') { | ||
| meetingType = localStorage.getItem(`meeting_${id}_meetingType`) || ''; | ||
| categoryParam = localStorage.getItem(`meeting_${id}_category`) || ''; | ||
| } | ||
|
|
||
| if (!meetingType && meetingData?.data?.purposes && meetingData.data.purposes.length > 0) { | ||
|
|
||
| if ( | ||
| !meetingType && | ||
| meetingData?.data?.purposes && | ||
| meetingData.data.purposes.length > 0 | ||
| ) { | ||
| meetingType = meetingData.data.purposes[0]; | ||
| } | ||
| if (!categoryParam && category) { | ||
| categoryParam = category; | ||
| } | ||
|
|
||
| const params = new URLSearchParams({ | ||
| meetingId: id, | ||
| midPlace: result.endStation, | ||
| lat: result.latitude.toString(), | ||
| lng: result.longitude.toString(), | ||
| }); | ||
|
|
||
| if (meetingType) params.append('meetingType', meetingType); | ||
| if (categoryParam) params.append('category', categoryParam); | ||
|
|
||
| router.push(`/recommend?${params.toString()}`); | ||
| }; | ||
|
|
||
| return ( | ||
| <div | ||
| key={result.id} | ||
|
|
@@ -293,33 +255,31 @@ export default function Page() { | |
| : 'border-gray-2 hover:bg-gray-1' | ||
| }`} | ||
| > | ||
|
|
||
| <div className="flex items-center gap-1.5"> | ||
| <Image src='/icon/stars.svg' alt='stars' width={16} height={16} /> | ||
| <span className="text-blue-5 text-sm font-medium">{categoryText}</span> | ||
| <Image src="/icon/stars.svg" alt="stars" width={16} height={16} /> | ||
| <span className="text-blue-5 text-sm font-medium"> | ||
| {categoryText} | ||
| </span> | ||
| </div> | ||
|
|
||
|
|
||
|
|
||
| <div className="flex items-center justify-between"> | ||
| <span className="text-gray-10 text-xl font-bold"> | ||
| {result.endStation}역 | ||
| </span> | ||
| <div className="flex flex-col items-end "> | ||
| <span className="text-gray-6 text-[13px] font-regular"> | ||
| <div className="flex flex-col items-end"> | ||
| <span className="text-gray-6 text-[13px] font-normal"> | ||
| 평균 이동시간{' '} | ||
| <span className="text-blue-5 text-[18px] font-bold"> | ||
| {result.averageTravelTime}분 | ||
| </span> | ||
| </span> | ||
|
|
||
| </div> | ||
| </div> | ||
|
|
||
|
|
||
|
|
||
| <div className="flex gap-2"> | ||
| <button | ||
| onClick={handleRecommendClick} | ||
| className="bg-gray-8 hover:bg-gray-9 flex-1 h-10 cursor-pointer rounded-[4px] text-[15px] font-normal text-white transition-colors" | ||
| className="bg-gray-8 hover:bg-gray-9 h-10 flex-1 cursor-pointer rounded-[4px] text-[15px] font-normal text-white transition-colors" | ||
| type="button" | ||
| > | ||
| 주변 장소 추천 | ||
|
|
@@ -329,14 +289,15 @@ export default function Page() { | |
| e.stopPropagation(); | ||
| openModal( | ||
| 'TRANSFER', | ||
| { meetingId: id, | ||
| { | ||
| meetingId: id, | ||
| userRoutes: result.userRoutes, | ||
| endStation: result.endStation, | ||
| }, | ||
| e | ||
| ); | ||
| }} | ||
| className="bg-gray-1 hover:bg-gray-2 flex-1 h-10 cursor-pointer rounded-[4px] text-[15px] font-normal text-blue-5 transition-colors" | ||
| className="bg-gray-1 hover:bg-gray-2 text-blue-5 h-10 flex-1 cursor-pointer rounded-[4px] text-[15px] font-normal transition-colors" | ||
| type="button" | ||
| > | ||
| 환승 경로 보기 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
한글 종성 판별 시 비한글 문자에 대한 가드 누락
category의 마지막 글자가 한글 음절(U+AC00~U+D7AF) 범위가 아닌 경우(숫자, 영문, 특수문자 등),(lastChar - 0xAC00) % 28계산이 잘못된 결과를 반환합니다. 예를 들어 카테고리가"카페2"일 경우"이"가 잘못 붙을 수 있습니다.🛡️ 한글 범위 체크 추가 제안
📝 Committable suggestion
🤖 Prompt for AI Agents