From 1072a5eb21749a2e6c8e1da1d3d5aed781dd111a Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 24 Apr 2026 01:14:54 +0900 Subject: [PATCH] =?UTF-8?q?fix(travel):=20useTravelData=20AbortController?= =?UTF-8?q?=20=EB=B0=8F=20=EC=97=90=EB=9F=AC=20=ED=95=B8=EB=93=A4=EB=A7=81?= =?UTF-8?q?=20=EB=B3=B4=EC=99=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/pages/travel/useTravelData.js | 49 +++++++++++++++++++------------ 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/pages/travel/useTravelData.js b/src/pages/travel/useTravelData.js index 9173425..f885a83 100644 --- a/src/pages/travel/useTravelData.js +++ b/src/pages/travel/useTravelData.js @@ -73,6 +73,7 @@ const useTravelData = () => { const currentAlbumRef = useRef(null); // { regionId, albumName } const cacheRef = useRef(new Map()); // photo data cache key: `${regionId}::${albumName}` const albumCacheRef = useRef(new Map()); // album metadata cache key: regionId + const loadAbortRef = useRef(null); // AbortController for loadAlbumPhotos /* ── Load GeoJSON regions once ──────────── */ useEffect(() => { @@ -195,7 +196,10 @@ const useTravelData = () => { setHasNext(false); pageRef.current = 1; + // Abort any in-flight loadAlbumPhotos request + if (loadAbortRef.current) loadAbortRef.current.abort(); const controller = new AbortController(); + loadAbortRef.current = controller; try { let url = `/api/travel/photos?region=${encodeURIComponent(regionId)}&page=1&size=${PAGE_SIZE}`; if (albumName) url += `&album=${encodeURIComponent(albumName)}`; @@ -241,11 +245,12 @@ const useTravelData = () => { setLoadingMore(true); setError(''); + const moreController = new AbortController(); try { let url = `/api/travel/photos?region=${encodeURIComponent(activeRegion)}&page=${pageRef.current}&size=${PAGE_SIZE}`; if (activeAlbum) url += `&album=${encodeURIComponent(activeAlbum)}`; - const res = await fetch(url); + const res = await fetch(url, { signal: moreController.signal }); if (!res.ok) throw new Error(`사진 로딩 실패 (${res.status})`); const json = await res.json(); const { normalized, hasNext: hn, summary } = parsePhotoResponse(json); @@ -289,26 +294,32 @@ const useTravelData = () => { let url = `/api/travel/photos?region=${encodeURIComponent(activeRegion)}&page=1&size=${PAGE_SIZE}`; if (activeAlbum) url += `&album=${encodeURIComponent(activeAlbum)}`; - const res = await fetch(url); - if (!res.ok) throw new Error(`사진 로딩 실패 (${res.status})`); - const json = await res.json(); - const { normalized, hasNext: hn, summary } = parsePhotoResponse(json); + const reloadController = new AbortController(); + try { + const res = await fetch(url, { signal: reloadController.signal }); + if (!res.ok) throw new Error(`사진 로딩 실패 (${res.status})`); + const json = await res.json(); + const { normalized, hasNext: hn, summary } = parsePhotoResponse(json); - const filtered = activeAlbum - ? normalized.filter((p) => !p.album || p.album === activeAlbum) - : normalized; + const filtered = activeAlbum + ? normalized.filter((p) => !p.album || p.album === activeAlbum) + : normalized; - pageRef.current = 2; - setPhotos(filtered); - setHasNext(hn); - cacheRef.current.set(cacheKey, { - timestamp: Date.now(), - items: filtered, - page: 2, - hasNext: hn, - summary, - }); - if (summary) setPhotoSummary(summary); + pageRef.current = 2; + setPhotos(filtered); + setHasNext(hn); + cacheRef.current.set(cacheKey, { + timestamp: Date.now(), + items: filtered, + page: 2, + hasNext: hn, + summary, + }); + if (summary) setPhotoSummary(summary); + } catch (err) { + if (err?.name === 'AbortError') return; + setError(err?.message ?? String(err)); + } }, []); /* ── getFilteredAlbums — filter by region ─ */