-
Notifications
You must be signed in to change notification settings - Fork 1
[Fix] 지도페이지 검색 결과 및 찜 기능 오류 수정 #73
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
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
## Walkthrough
이 변경사항은 지도 및 검색 페이지의 마커 렌더링과 상호작용을 위한 새로운 컴포넌트(`MapViewer`)와 커스텀 훅(`useMapViewer`)을 도입하고, 좋아요 토글 기능에 동시성 제어 및 서버-로컬 상태 동기화를 추가합니다. 또한, 관련 컴포넌트와 훅의 경로 및 호출 방식을 정비하였습니다.
## Changes
| 파일/경로 그룹 | 변경 요약 |
|-----------------------------------------------------------|-----------|
| src/pages/map/components/PlaceContent.jsx | 좋아요 버튼 클릭 시 동시성 제어 및 에러 핸들링을 위한 `isLiking` 상태와 `handleLikeClick` 비동기 함수 추가. 기존 onClick 핸들러를 대체. |
| src/pages/map/hooks/useToggleLike.js | 서버와 로컬의 좋아요 상태 동기화 및 동시성 제어(`isProcessing` 상태) 추가. 서버 상태 확인 후 토글, 에러 및 로딩 상태 관리 강화. |
| src/pages/search/SearchPage.jsx | `MapViewer` 컴포넌트 import 경로를 `"../map/components/MapViewer"`에서 `"./components/MapViewer"`로 변경. `useToggleLike` 훅 도입 및 좋아요 토글 로직 대체, 선택 장소에 liked 속성 추가, 지도 관련 상태 및 콜백 추가, 위치 이동 버튼 추가. |
| src/pages/search/components/MapViewer.jsx | 새로운 `MapViewer` 리액트 컴포넌트 추가. 지도 렌더링 및 마커 상호작용을 위한 props와 mapRef, 커스텀 훅 호출 포함. `disableAutoUserPan` 옵션 추가. |
| src/pages/search/hooks/useMapViewer.js | 지도 렌더링, 마커 동기화, 사용자 위치 이동, 마커 클릭 이벤트 등 지도 관련 로직을 관리하는 새로운 커스텀 훅 `useMapViewer` 추가. 자동 사용자 위치 이동 비활성화 옵션 포함. |
| src/pages/map/components/PlaceBottomSheet.jsx | `PlaceBottomSheet` 컴포넌트에 `onHeightChange` 콜백 prop 추가. 바텀 시트 높이 변경 시 해당 콜백 호출하도록 수정. |
| src/pages/join/components/LocationStep.jsx | 백버튼 위치를 절대에서 고정(fixed)으로 변경하고 크기 및 접근성 속성(aria-label) 추가, 상단 여백 조정. |
| src/pages/join/components/NameStep.jsx | 백버튼 위치를 절대에서 고정(fixed)으로 변경, 버튼 및 아이콘 크기 확대, 제목 텍스트 멀티라인 JSX로 변경, submit 버튼 클래스명 간소화. |
| src/pages/join/components/OwnerStep.jsx | 백버튼 위치를 절대에서 고정(fixed)으로 변경, 버튼 크기 확대 및 aria-label 추가, 상단 여백 조정. |
| src/pages/join/components/ProfileImageStep.jsx | 백버튼 위치를 절대에서 고정(fixed)으로 변경, 버튼 및 아이콘 크기 확대, 이미지 alt 텍스트 수정. |
## Possibly related PRs
- Morak-Team/Frontend#71: 좋아요 토글 인증 체크 및 로그인 상태 처리 개선과 본 PR의 좋아요 토글 비동기 처리 및 에러 핸들링 강화가 기능적으로 연관됨.
- Morak-Team/Frontend#42: 좋아요 상태 관리 및 UI 컴포넌트 관련 지도 페이지 개선과 연관된 PR로, `PlaceContent.jsx`의 좋아요 토글 동시성 제어와 관련됨.
## Suggested reviewers
- dbstj0403Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (4)
✅ Files skipped from review due to trivial changes (2)
🔇 Additional comments (2)
✨ Finishing Touches
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. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 4
🧹 Nitpick comments (8)
src/pages/search/SearchPage.jsx (1)
13-14: MapViewer 경로 확인 및 alias 사용 고려
"./components/MapViewer"로 상대경로를 변경하신 부분은 문제 없어 보이나, 동일한 컴포넌트를 다른 페이지에서도 재사용할 가능성이 있다면@pages/search/components/MapViewer와 같은 절대경로(alias)로 통일하면 추후 리팩터링·이동 시 import 수정 비용을 줄일 수 있습니다.
경로 alias 가 이미 설정돼 있다면 적용을 검토해 주세요.src/pages/map/components/PlaceContent.jsx (2)
27-28: 버튼 비활성화·로딩 UI 제공으로 사용자 경험 개선
isLiking플래그로 중복 클릭은 방지했지만, UI 상에서 로딩 상태가 표시되지 않아 “눌렀는데 반응이 없다” 는 인상을 줄 수 있습니다.
아래처럼disabled와aria-busy속성을 추가하고 스피너를 노출하면 UX 가 좋아집니다.-<button - onClick={handleLikeClick} - className="w-14 h-14 flex items-center justify-center rounded-md bg-gray-2" -> +<button + onClick={handleLikeClick} + disabled={isLiking} + aria-busy={isLiking} + className={`w-14 h-14 flex items-center justify-center rounded-md bg-gray-2 ${isLiking ? "opacity-50 cursor-not-allowed" : ""}`} +>Also applies to: 175-177
80-92:id상수 사용 및 예외 전파 검토
- 이미 52 라인에서
id를 구조 분해 해두셨으므로place.id대신id를 사용하면 중복을 줄일 수 있습니다.onToggleLike내부에서 실패하면 현재handleLikeClick이 단순 로그만 남기고 끝나는데, 부모 컴포넌트가 추가 처리를 할 수 있도록 예외를 다시 throw 하거나 결과를 반환하는 방식을 고려해 보세요.- await onToggleLike?.(place.id); + await onToggleLike?.(id);src/pages/map/hooks/useToggleLike.js (2)
4-8: API 모듈 분리 권장
likeCompany,unlikeCompany,getLikedCompanies세 함수가 모두@apis/company/getLikedCompanies에서 export 되는 구조는 이름과 파일 내용이 불일치해 가독성을 떨어뜨립니다.
파일을likeCompany.js,unlikeCompany.js,getLikedCompanies.js로 분리하거나index.js에서 재-export 하여 명확히 해 주세요.
38-56: 매 토글 시getLikedCompanies호출로 인한 과도한 네트워크 비용토글마다 전체 찜 목록을 가져오면 목록이 클수록 지연이 커집니다.
- 최초 페이지 진입 시 한 번만 가져와 캐싱하고,
- 토글 성공 후 캐시를 낙관적으로 갱신하거나,
- 불일치 발생 시에만 서버 확인을 수행하는 방식으로 호출 횟수를 최소화해 보세요.
이전 호출 결과를
useRef로 보관하거나 SWR / React-Query 캐시를 활용하면 간단히 구현할 수 있습니다.src/pages/search/components/MapViewer.jsx (1)
4-16: PropTypes/타입 선언 추가 권장다양한 콜백과 옵션을 받는 컴포넌트이므로 런타임 오류를 방지하려면
prop-types(JS) 또는TS변환을 통해 명시적 타입 선언을 추가하는 것이 좋습니다.src/pages/search/hooks/useMapViewer.js (2)
30-40: 모든 마커 아이콘을 매 클릭마다 리렌더링 → 성능 최적화 여지현재 클릭 시 전체 마커 아이콘을 일괄 초기화한 뒤 하나만 하이라이트합니다. 마커 수가 많을 경우 불필요한 연산이므로 이전 선택 마커를 기억해 두고 두 개만 업데이트하도록 개선할 수 있습니다.
79-82: 네이버 이벤트 리스너 해제 누락 → 메모리 릭 위험컴포넌트 언마운트 시
window.naver.maps.Event.removeListener를 호출하지 않으면 리스너와 마커가 메모리에 남습니다.useEffect클린업에서 리스너/마커를 해제해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/pages/map/components/PlaceContent.jsx(3 hunks)src/pages/map/hooks/useToggleLike.js(2 hunks)src/pages/search/SearchPage.jsx(1 hunks)src/pages/search/components/MapViewer.jsx(1 hunks)src/pages/search/hooks/useMapViewer.js(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/pages/map/components/PlaceContent.jsx (1)
src/pages/review/components/PlaceInfo.jsx (1)
handleLikeClick(47-69)
| const [isProcessing, setIsProcessing] = useState(false); | ||
| const navigate = useNavigate(); | ||
|
|
||
| const toggleLike = async (targetId) => { | ||
| const isAuthenticated = await useAuthStore.getState().checkAuth(); | ||
|
|
||
| if (!isAuthenticated) { | ||
| if (onRequireLogin) { | ||
| onRequireLogin(); | ||
| } else { | ||
| navigate("/auth"); | ||
| } | ||
| onRequireLogin ? onRequireLogin() : navigate("/auth"); | ||
| return; | ||
| } | ||
|
|
||
| const currentPlace = placesWithDistance.find((p) => p.id === targetId); | ||
| if (!currentPlace) return; | ||
| if (!currentPlace || isProcessing) return; | ||
|
|
||
| setIsProcessing(true); | ||
| setLoading(true); | ||
|
|
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.
🛠️ Refactor suggestion
전역 isProcessing 플래그로 인한 다중 토글 제한
isProcessing 이 훅 단위로 하나만 존재하기 때문에 A 기업 “찜” 도중 B 기업을 바로 “찜“ 하려면 첫 요청이 끝날 때까지 대기해야 합니다.
사용자는 여러 기업을 연속으로 빠르게 찜할 수 있기를 기대할 수 있으므로, 기업 ID 별로 별도 lock 을 두거나 Set 형태로 진행 중 ID 를 저장하는 방식을 고려해 보세요.
| useMapViewer({ | ||
| mapRef, | ||
| places, | ||
| onMarkerClick, | ||
| userCoords, | ||
| moveToCurrentLocation, | ||
| onMoveComplete, | ||
| resetMap, | ||
| center, | ||
| markerPosition, | ||
| zoom, | ||
| selectedPlace, | ||
| }); |
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.
showOnlyLiked 프로퍼티가 훅에 전달되지 않아 필터/하이라이트 기능이 무력화됩니다
MapViewer 컴포넌트는 showOnlyLiked 값을 인자로 받지만, useMapViewer 호출 시 누락되어 훅 내부에서는 항상 undefined 로 평가됩니다.
그 결과
handleMarkerClick내부에서liked여부가 의도대로 반영되지 않고,- 훅의 의존성 배열에 들어 있어도 값이 변해도 업데이트되지 않습니다.
useMapViewer({
mapRef,
places,
onMarkerClick,
userCoords,
moveToCurrentLocation,
onMoveComplete,
resetMap,
center,
markerPosition,
zoom,
selectedPlace,
+ showOnlyLiked,
});📝 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.
| useMapViewer({ | |
| mapRef, | |
| places, | |
| onMarkerClick, | |
| userCoords, | |
| moveToCurrentLocation, | |
| onMoveComplete, | |
| resetMap, | |
| center, | |
| markerPosition, | |
| zoom, | |
| selectedPlace, | |
| }); | |
| useMapViewer({ | |
| mapRef, | |
| places, | |
| onMarkerClick, | |
| userCoords, | |
| moveToCurrentLocation, | |
| onMoveComplete, | |
| resetMap, | |
| center, | |
| markerPosition, | |
| zoom, | |
| selectedPlace, | |
| showOnlyLiked, | |
| }); |
| if (!marker) { | ||
| const newMarker = new window.naver.maps.Marker({ | ||
| position: new window.naver.maps.LatLng( | ||
| place.coords.lat, | ||
| place.coords.lng, | ||
| ), | ||
| map: mapInstance.current, | ||
| icon: createMarkerIcon(isHighlighted, place.liked), | ||
| }); |
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.
좌표가 없는 Place 처리 시 런타임 오류 가능
마커가 없어서 새로 생성할 때 place.coords.lat 접근을 무조건 수행합니다. coords가 없을 수 있는 경우를 위에서 필터링하지 않아 TypeError가 발생할 여지가 있습니다.
- if (!marker) {
+ if (!marker && place.coords?.lat && place.coords?.lng) {또는 초기에 coords 유효성 검사를 추가해 주세요.
📝 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.
| if (!marker) { | |
| const newMarker = new window.naver.maps.Marker({ | |
| position: new window.naver.maps.LatLng( | |
| place.coords.lat, | |
| place.coords.lng, | |
| ), | |
| map: mapInstance.current, | |
| icon: createMarkerIcon(isHighlighted, place.liked), | |
| }); | |
| if (!marker && place.coords?.lat && place.coords?.lng) { | |
| const newMarker = new window.naver.maps.Marker({ | |
| position: new window.naver.maps.LatLng( | |
| place.coords.lat, | |
| place.coords.lng, | |
| ), | |
| map: mapInstance.current, | |
| icon: createMarkerIcon(isHighlighted, place.liked), | |
| }); |
| useEffect(() => { | ||
| if (isMapInitialized || (!userCoords && !center)) return; | ||
|
|
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.
지도 초기화 조건이 과도하게 제한적입니다
(!userCoords && !center) 조건 때문에 사용자 좌표와 센터가 모두 없으면 지도가 영원히 초기화되지 않습니다. 이미 기본 좌표(서울 시청)를 설정하고 있으므로 불필요한 early-return 입니다.
- if (isMapInitialized || (!userCoords && !center)) return;
+ if (isMapInitialized) return;📝 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.
| useEffect(() => { | |
| if (isMapInitialized || (!userCoords && !center)) return; | |
| useEffect(() => { | |
| - if (isMapInitialized || (!userCoords && !center)) return; | |
| + if (isMapInitialized) return; |
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.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
src/pages/map/components/PlaceBottomSheet.jsx (1)
110-115: 🛠️ Refactor suggestion
handleClickExpand에서onHeightChange가 호출되지 않습니다
데스크톱(또는isMobile === false) 환경에서 헤더 바를 클릭해 시트를 확장할 경우,controls.start({ height: MAX_HEIGHT.current })호출 이후bottomSheetHeight(부모에서 관리)가 갱신되지 않습니다. 이로 인해
SearchPage에서 떠 있는 "현재 위치" 플로팅 버튼이 계속 보이거나,- 다른 UI가 시트 실제 높이와 동기화되지 않는 문제가 발생합니다.
const handleClickExpand = () => { if (!isMobile && !isExpanded) { controls.start({ height: MAX_HEIGHT.current }); setIsExpanded(true); + onHeightChange?.(MAX_HEIGHT.current); // 확장 시 높이 반영 } };
♻️ Duplicate comments (2)
src/pages/search/hooks/useMapViewer.js (2)
47-48: 지도 초기화 조건이 여전히 과도하게 제한적입니다
(!userCoords && !center)조건으로 인해 사용자 좌표와 center 둘 다 없을 때 지도 초기화가 건너뛰어집니다. 기본 좌표(서울 시청)를 이미 설정하고 있기 때문에 해당 조건문은 필요하지 않습니다.과거 커밋에서도 동일 지적이 있었으므로 중복 코멘트로 표시합니다.
- if (isMapInitialized || (!userCoords && !center)) return; + if (isMapInitialized) return;
191-206:⚠️ Potential issuecoords 유효성 검증 누락 → 런타임 오류 위험
마커 업데이트 단계에서place.coords.lat접근 전 null / undefined 체크가 없습니다.places배열에 좌표 정보가 없는 객체가 포함될 경우TypeError가 발생할 수 있습니다.- places.forEach((place) => { + places.forEach((place) => { + if (!place.coords?.lat || !place.coords?.lng) return; // 안전 가드
🧹 Nitpick comments (4)
src/pages/map/components/PlaceBottomSheet.jsx (1)
184-185: 캡처 성공 시점에 상태를 두 번 set 하는 부분 확인
setCompanyInfo및setShowConfirm(true)호출 후, 상위 흐름에서companyInfo를 다시null로 만드는 로직이 있어 불필요하게 상태 변동이 두 번 발생할 수 있습니다. 성능 영향은 미미하지만 필요하다면 한 번의 상태 객체로 묶는 것도 고려해보세요.src/pages/search/SearchPage.jsx (2)
226-230:disableAutoUserPan기본값 재확인
검색 페이지에서는 최초 진입 시 자동으로 유저 위치로 이동하지 않도록disableAutoUserPan={true}를 전달했습니다. UX 관점에서 검색된 장소가 없는 경우 혹은 유저가 지도 첫 진입 시 자동 패닝이 더 자연스러울 수 있으니 의도한 동작인지 한번 검토해 주세요.
232-249: 플로팅 버튼 위치·노출 조건 상수 중복
bottomSheetHeight <= 220과PlaceBottomSheet내부MIN_HEIGHT = 220값이 하드코딩으로 중복됩니다. 추후 설계 변경 시 두 곳을 모두 수정해야 하므로,
PlaceBottomSheet.MIN_HEIGHT식으로 외부에 상수를 공개하거나- 공통 constants 파일에 정의해 import
하도록 리팩터링하면 유지보수성이 올라갑니다.
src/pages/search/hooks/useMapViewer.js (1)
110-139:setTimeout클린업은 좋지만, ref flag 초기화 고려
hasAnimatedRef.current = true로 한 번만 자동 패닝하도록 막고 있는데resetMap후 다시 자동 패닝이 필요하면 flag 를 초기화할 수 없습니다. 필요 시resetMapeffect 에서hasAnimatedRef.current = false로 되돌려 주는 로직을 추가해 주세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/pages/map/components/PlaceBottomSheet.jsx(4 hunks)src/pages/search/SearchPage.jsx(5 hunks)src/pages/search/components/MapViewer.jsx(1 hunks)src/pages/search/hooks/useMapViewer.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/pages/search/components/MapViewer.jsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/pages/search/hooks/useMapViewer.js (4)
src/pages/map/components/PlaceContent.jsx (1)
place(51-65)src/pages/map/MapPage.jsx (2)
showOnlyLiked(22-22)places(21-21)src/pages/search/SearchPage.jsx (3)
userCoords(32-32)selectedPlace(22-22)moveToCurrentLocation(23-23)src/pages/search/components/MapViewer.jsx (1)
mapRef(18-18)
🔇 Additional comments (4)
src/pages/map/components/PlaceBottomSheet.jsx (2)
94-107:handleTouchEnd분기별onHeightChange호출은 좋지만, 중복‧누락 여부를 확인해주세요
delta < -50/delta > 100/else세 분기에서 모두onHeightChange가 호출되어 일관성이 있습니다.- 단, 위 코멘트처럼
handleClickExpand에서의 누락은 여전히 존재합니다.
68-70: 마운트 시점 높이 전달 👍
컴포넌트 초기 렌더에서MIN_HEIGHT값을 부모에 알려주는 로직이 추가되어 의존성 없는 초기 상태도 동기화됩니다.src/pages/search/SearchPage.jsx (2)
144-152:useToggleLike훅에 빈 setter 전달 → 좋아요 상태가 갱신되지 않을 수 있음
현재 페이지는placesWithDistance를[selectedPlace]만 넘기고,setPlacesWithDistance/setFilteredPlaces를 모두() => {}로 넘기고 있습니다.
useToggleLike내부 구현이 immutable 업데이트 후 이 두 setter를 통해 상위 리스트를 갱신하도록 설계되었다면, 좋아요 토글이 서버에는 반영되더라도 UI에는 즉시 반영되지 않을 가능성이 있습니다.
- 최소한
setSelectedPlace로직이 동작해 토스트/아이콘만 바꿔도 되는지,- 아니면 간단한 로컬 상태 업데이트(예:
setSelectedPlace({...selectedPlace, liked: !selectedPlace.liked}))를 훅 옵션으로 제공할지 확인해 주세요.
23-24:moveToCurrentLocation상태 초기값 검토
초기값이false라 문제는 없지만, URL 파라미터 등으로 현재 위치 이동을 바로 실행하고 싶을 때는 초기값을 조건부로 줄 수 있도록 고려해 볼 수 있습니다.




🌟 어떤 이유로 MR를 하셨나요?
📝 세부 내용 - 왜 해당 MR이 필요한지 작업 내용을 자세하게 설명해주세요
📸 작업 화면 스크린샷
2025-05-13.12.47.16.mov
🚨 관련 이슈 번호
Summary by CodeRabbit
신규 기능
버그 수정
스타일 및 레이아웃 개선