[FIX] 테스트 기능 수정 - #340
Conversation
📋 Walkthrough테스트 기능 완성을 위해 페이지 네비게이션 흐름을 개선하고, 테스트 결과를 sessionStorage에 저장하여 관리하며, 이전 페이지 경로를 localStorage에 저장하여 동적 리다이렉션을 구현합니다. 또한 추천 섹션으로 스크롤하는 기능을 추가하고, 결과 페이지의 모바일 공유 기능을 확대합니다. 🗂️ Changes
🔄 Sequence Diagram(s)sequenceDiagram
participant User
participant TestPage as Test Page
participant API as Test API
participant SessionStor as sessionStorage
participant ResultPage as Result Page
participant AuthAPI as Auth API
participant MainPage as Main Page
User->>TestPage: 테스트 시작 (prevPage 저장)
TestPage->>SessionStor: localStorage('prevPage') 저장
User->>API: 테스트 제출
API->>SessionStor: test-result 저장
API->>ResultPage: 결과 페이지로 이동
alt 로그인되지 않음
ResultPage->>AuthAPI: 로그인
AuthAPI->>SessionStor: type 확인
AuthAPI->>MainPage: scrollTo=recommend 파라미터로 리다이렉트
MainPage->>User: 추천 섹션으로 스크롤
else 로그인된 상태
ResultPage->>SessionStor: prevPath에서 복구
ResultPage->>User: 이전 페이지로 이동
end
🎯 Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🔗 Possibly related PRs
👥 Suggested reviewers
🐰 Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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. Comment |
|
🪷 Storybook 확인 🪷 |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/test/page.tsx (1)
6-18: ESLint import/order 규칙 위반 정정 필요현재 imports가 ESLint import/order 규칙을 위반합니다. 설정된 규칙에 따라 다음 순서로 정렬되어야 합니다: builtin (React, next/navigation) → external (cookies-next) → sibling/parent (./testPage.css) → internal (
@/*) 각 그룹 사이에는 빈 줄이 필요합니다.eslint --fix로 자동 정렬하거나 수동으로 재정렬해주세요.
🤖 Fix all issues with AI agents
In `@src/apis/auth/index.ts`:
- Around line 34-47: The current flow always removes the session key from
sessionStorage in the finally block which prevents retry on
saveTestResultMutation.mutateAsync failure; change the logic so
sessionStorage.removeItem('type') is only called after a successful await of
saveTestResultMutation.mutateAsync (move it into the try after the await), add a
catch block to handle errors from saveTestResultMutation.mutateAsync (log or
surface an error/toast and leave the 'type' in sessionStorage for retry), and
ensure router.push(url) still runs with the correct url determined on success or
failure; refer to sessionStorage.getItem('type'),
saveTestResultMutation.mutateAsync, sessionStorage.removeItem('type'), and
router.push when making the change.
In `@src/app/test/page.tsx`:
- Line 26: The default for prevPath returned from getStorageValue('prevPage') is
an empty string which causes router.push('') to be called (invalid in Next.js);
change the default from '' to '/' so prevPath = getStorageValue('prevPage') ||
'/' and ensure any subsequent router.push(prevPath) receives a valid path; apply
the same change to other places using getStorageValue('prevPage') (e.g., in
SearchResultPageClient and the test result page) and verify router.push calls
rely on the sanitized prevPath variable.
In `@src/app/test/result/page.tsx`:
- Line 33: Change the default prevPath from an empty string to '/' so
router.push won't be called with an empty path: update the assignment where
prevPath is set (const prevPath = getStorageValue('prevPage') || '') to use '/'
as the fallback; ensure any subsequent usage such as router.push(prevPath) in
the same file (page.tsx) will correctly navigate to the home page when
getStorageValue('prevPage') returns null/undefined.
- Around line 5-26: Reorder the import statements to satisfy import/order: group
and sort imports, remove blank lines inside each group and keep a single blank
line between groups; specifically alphabetize external libs (e.g., move
`@tanstack/react-query`, cookies-next, html-to-image into correct alphabetical
order), then alphabetize component imports (PageBottomBtn, PopupBtn, KakaoBtn,
Bubble, ResultCard, TestHeader, ExceptLayout) and utility/hooks/constants
imports (getStorageValue, getTestType, TestType) within their groups, keep
next/dynamic, next/image, next/navigation in the framework group, and leave the
styles import in the local group; you can run npx eslint --fix to apply
auto-fixes or reorder manually ensuring no extra blank lines inside groups and
only one blank line between groups.
In `@src/hooks/useScrollToTarget.ts`:
- Around line 12-37: The useScrollToTarget effect (useScrollToTarget) currently
only relies on a MutationObserver so if the target element already exists at
effect run time the scroll is never triggered; fix by first checking
document.getElementById(targetId) immediately and calling performScroll(element)
if found, before instantiating the MutationObserver, otherwise keep the observer
logic as-is; reference performScroll, MutationObserver instance (observer) and
observer.observe to locate where to add the immediate existence check and early
performScroll call.
🧹 Nitpick comments (2)
src/constants/exceptInfos.ts (1)
12-16: 빈 subtitle 렌더링으로 여백이 남을 수 있습니다.
의도적으로 숨기는 거라면null/undefined로 두고, 레이아웃에서 falsy면 렌더링하지 않도록 처리하는 편이 깔끔합니다.src/apis/test/index.ts (1)
20-31: sessionStorage 예외 대비가 있으면 더 안전합니다.
차단된 환경(예: 일부 프라이빗 모드)에서setItem이 예외를 던지면 이후 캐시/라우팅이 중단될 수 있어요. try/catch로 보호하는 쪽을 권장합니다.🔒️ 방어 코드 예시
- sessionStorage.setItem('test-result', JSON.stringify(data)); + try { + sessionStorage.setItem('test-result', JSON.stringify(data)); + } catch (e) { + console.error(e); + }
| // 비회원 상태에서 테스트 완료 후 로그인한 경우 테스트 결과 저장 | ||
| const type = sessionStorage.getItem('type') as TestType; | ||
|
|
||
| let url = '/'; | ||
|
|
||
| if (type) { | ||
| try { | ||
| await saveTestResultMutation.mutateAsync(type); | ||
| url = '/?scrollTo=recommend'; | ||
| } finally { | ||
| sessionStorage.removeItem('type'); | ||
| } | ||
| } | ||
| router.push('/'); | ||
| router.push(url); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check if the file exists and view the exact code at lines 34-47
if [ -f "src/apis/auth/index.ts" ]; then
echo "=== File content at lines 34-47 ==="
sed -n '34,47p' src/apis/auth/index.ts | cat -n
echo ""
echo "=== Full context (lines 1-60) ==="
head -60 src/apis/auth/index.ts | cat -n
else
echo "File not found: src/apis/auth/index.ts"
find . -name "index.ts" -path "*/auth/*" 2>/dev/null
fiRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 2724
🏁 Script executed:
# Search for saveTestResultMutation definition
rg "saveTestResultMutation" --max-count 20 -B 2 -A 2Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 598
🏁 Script executed:
# Search for how sessionStorage 'type' is used elsewhere - check for retry patterns
rg "sessionStorage.*type|type.*sessionStorage" -B 2 -A 2Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 928
저장 실패 시 type 삭제로 재시도 불가합니다.
mutateAsync 실패 시 finally에서 type을 지워서 결과 유실이 발생할 수 있습니다. 성공 시에만 삭제하거나 실패 시 별도 처리/재시도 방안을 추가해주세요.
🛠️ 수정 제안
if (type) {
try {
await saveTestResultMutation.mutateAsync(type);
url = '/?scrollTo=recommend';
+ sessionStorage.removeItem('type');
} catch (e) {
+ console.error(e);
+ // 실패 시 type을 유지해 재시도 여지를 남기는 방안 고려
}
- } finally {
- sessionStorage.removeItem('type');
- }
}📝 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 type = sessionStorage.getItem('type') as TestType; | |
| let url = '/'; | |
| if (type) { | |
| try { | |
| await saveTestResultMutation.mutateAsync(type); | |
| url = '/?scrollTo=recommend'; | |
| } finally { | |
| sessionStorage.removeItem('type'); | |
| } | |
| } | |
| router.push('/'); | |
| router.push(url); | |
| if (type) { | |
| try { | |
| await saveTestResultMutation.mutateAsync(type); | |
| url = '/?scrollTo=recommend'; | |
| sessionStorage.removeItem('type'); | |
| } catch (e) { | |
| console.error(e); | |
| // 실패 시 type을 유지해 재시도 여지를 남기는 방안 고려 | |
| } | |
| } | |
| router.push(url); |
🤖 Prompt for AI Agents
In `@src/apis/auth/index.ts` around lines 34 - 47, The current flow always removes
the session key from sessionStorage in the finally block which prevents retry on
saveTestResultMutation.mutateAsync failure; change the logic so
sessionStorage.removeItem('type') is only called after a successful await of
saveTestResultMutation.mutateAsync (move it into the try after the await), add a
catch block to handle errors from saveTestResultMutation.mutateAsync (log or
surface an error/toast and leave the 'type' in sessionStorage for retry), and
ensure router.push(url) still runs with the correct url determined on success or
failure; refer to sessionStorage.getItem('type'),
saveTestResultMutation.mutateAsync, sessionStorage.removeItem('type'), and
router.push when making the change.
| const [isModalOpen, setIsModalOpen] = useState(false); | ||
| const steps = ['START', ...TEST_STEPS.map((step) => step.id)]; | ||
| const hasType = getCookie('hasType'); | ||
| const prevPath = getStorageValue('prevPage') || ''; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check if the file exists and read the relevant sections
if [ -f "src/app/test/page.tsx" ]; then
echo "=== File exists, reading content ==="
wc -l src/app/test/page.tsx
echo ""
echo "=== Lines 20-80 context ==="
sed -n '20,80p' src/app/test/page.tsx | cat -n
else
echo "File not found. Checking directory structure..."
find . -name "page.tsx" -type f 2>/dev/null | head -20
fiRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 192
🏁 Script executed:
# Search for getStorageValue and router.push to understand the pattern
rg "getStorageValue|router\.push" --no-heading -B2 -A2Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 10638
🏁 Script executed:
# Read lines 60-80 to see the exact context of line 73
sed -n '60,80p' src/app/test/page.tsx | cat -nRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 753
🏁 Script executed:
# Check all files with the prevPage pattern to see if they all have the same issue
rg "prevPath = getStorageValue\('prevPage'\)" -A 20 --no-headingRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 3883
🌐 Web query:
Next.js router.push empty string behavior navigation
💡 Result:
Short answer: Next.js doesn't document a special meaning for router.push('') — passing an empty string is effectively undefined/unsupported and in practice behaves like a no-op (or a relative navigation to the current URL) depending on router/version. Use an explicit path (e.g. '/' or the current pathname) or call router.refresh() instead. [1][2]
Why / what to do:
- The official API only documents router.push(href: string | UrlObject, ...); it expects a real URL/href, so '' is not a supported explicit value. Use explicit hrefs. [1][2]
- There are multiple GitHub/Forum reports about surprising behaviour when pushing the same URL or updating only query params — same-URL pushes sometimes do nothing or trigger server fetches depending on App/Pages router and Next.js version. For reliable results use an explicit path, router.replace, or router.refresh() as appropriate. [3][4]
Sources:
- Next.js router.push docs (pages/app-router API). [1][2]
- Community issues / discussion describing same-URL / query-update quirks and router.push behavior. [3][4]
References:
[1] Next.js docs — useRouter / router.push (API).
[2] Next.js docs — Linking and Navigating (Routing).
[3] vercel/next.js GitHub issues discussing navigation behavior.
[4] Next.js forum / community threads about router.push and same-URL/query updates.
prevPath가 비어 있으면 라우팅이 작동하지 않을 수 있어요.
Line 26의 기본값이 ''일 때 Line 73에서 router.push('')가 호출되는데, Next.js에서 빈 문자열은 지원되지 않는 값이며 예상치 못한 동작(무시되거나 오류 발생)을 유발할 수 있습니다. 기본값을 '/'로 변경하세요. 이 패턴은 src/app/searchResult/SearchResultPageClient.tsx와 src/app/test/result/page.tsx에도 동일하게 적용됩니다.
🔧 제안 수정
- const prevPath = getStorageValue('prevPage') || '';
+ const prevPath = getStorageValue('prevPage') || '/';Also applies to: 73-73
🤖 Prompt for AI Agents
In `@src/app/test/page.tsx` at line 26, The default for prevPath returned from
getStorageValue('prevPage') is an empty string which causes router.push('') to
be called (invalid in Next.js); change the default from '' to '/' so prevPath =
getStorageValue('prevPage') || '/' and ensure any subsequent
router.push(prevPath) receives a valid path; apply the same change to other
places using getStorageValue('prevPage') (e.g., in SearchResultPageClient and
the test result page) and verify router.push calls rely on the sanitized
prevPath variable.
| import dynamic from 'next/dynamic'; | ||
| import Image from 'next/image'; | ||
| import { useRouter } from 'next/navigation'; | ||
|
|
||
| import { toPng } from 'html-to-image'; | ||
| import { getCookie } from 'cookies-next'; | ||
| import { useQueryClient } from '@tanstack/react-query'; | ||
|
|
||
| import { TestResponse } from '@apis/test/type'; | ||
| import PageBottomBtn from '@components/common/button/pageBottomBtn/PageBottomBtn'; | ||
| import KakaoBtn from '@components/common/button/kakaoBtn/KakaoBtn'; | ||
| import Bubble from '@components/common/bubble/Bubble'; | ||
| import PopupBtn from '@components/common/button/popupBtn/PopupBtn'; | ||
| import ResultCard from '@components/test/resultCard/ResultCard'; | ||
| import { useQueryClient } from '@tanstack/react-query'; | ||
| import { TestResponse } from '@apis/test/type'; | ||
| import getTestType from '@utils/getTestType'; | ||
| import { TestType } from '@constants/test'; | ||
| import { toPng } from 'html-to-image'; | ||
| import TestHeader from '@components/test/testHeader/TestHeader'; | ||
| import { useRouter } from 'next/navigation'; | ||
| import ExceptLayout from '@components/except/exceptLayout/ExceptLayout'; | ||
| import { getCookie } from 'cookies-next'; | ||
|
|
||
| import { getStorageValue } from '@hooks/useLocalStorage'; | ||
| import getTestType from '@utils/getTestType'; | ||
| import { TestType } from '@constants/test'; | ||
|
|
||
| import * as styles from './resultPage.css'; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for ESLint configuration files
fd -e '.eslintrc*' -o -e 'eslint.config.*' -o -e 'package.json'Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 192
🏁 Script executed:
#!/bin/bash
# List files in the repository root to understand structure
git ls-files | head -20Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 594
🏁 Script executed:
#!/bin/bash
# Check the actual content of the file
cat -n src/app/test/result/page.tsx | head -30Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 1425
🏁 Script executed:
cat -n eslint.config.jsRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 3776
🏁 Script executed:
npx eslint src/app/test/result/page.tsx --format=json 2>&1 | head -100Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 18895
import/order ESLint 규칙 위반 정리 필요
17개의 import/order 오류가 감지되었습니다. 다음을 수정하세요:
- 각 그룹 내 빈 줄 제거
- 외부 라이브러리 알파벳순 정렬:
@tanstack/react-query→cookies-next→html-to-image - 컴포넌트 경로 알파벳순 정렬
- 각 그룹 간에만 빈 줄 유지
npx eslint --fix로 자동 정렬하거나 수동으로 재정렬해주세요.
🧰 Tools
🪛 ESLint
[error] 5-5: next/dynamic import should occur after import of @constants/test
(import/order)
[error] 6-6: next/image import should occur after import of @constants/test
(import/order)
[error] 7-7: There should be no empty line within import group
(import/order)
[error] 7-7: next/navigation import should occur after import of @constants/test
(import/order)
[error] 9-9: html-to-image import should occur after import of @constants/test
(import/order)
[error] 10-10: cookies-next import should occur after import of @constants/test
(import/order)
[error] 11-11: There should be no empty line within import group
(import/order)
[error] 11-11: @tanstack/react-query import should occur after import of @constants/test
(import/order)
[error] 14-14: @components/common/button/pageBottomBtn/PageBottomBtn import should occur after import of @components/common/bubble/Bubble
(import/order)
[error] 15-15: @components/common/button/kakaoBtn/KakaoBtn import should occur after import of @components/common/bubble/Bubble
(import/order)
[error] 18-18: @components/test/resultCard/ResultCard import should occur after import of @components/except/exceptLayout/ExceptLayout
(import/order)
[error] 19-19: @components/test/testHeader/TestHeader import should occur after import of @components/except/exceptLayout/ExceptLayout
(import/order)
[error] 20-20: There should be no empty line within import group
(import/order)
[error] 22-22: @hooks/useLocalStorage import should occur after import of @constants/test
(import/order)
[error] 23-23: @utils/getTestType import should occur after import of @constants/test
(import/order)
🤖 Prompt for AI Agents
In `@src/app/test/result/page.tsx` around lines 5 - 26, Reorder the import
statements to satisfy import/order: group and sort imports, remove blank lines
inside each group and keep a single blank line between groups; specifically
alphabetize external libs (e.g., move `@tanstack/react-query`, cookies-next,
html-to-image into correct alphabetical order), then alphabetize component
imports (PageBottomBtn, PopupBtn, KakaoBtn, Bubble, ResultCard, TestHeader,
ExceptLayout) and utility/hooks/constants imports (getStorageValue, getTestType,
TestType) within their groups, keep next/dynamic, next/image, next/navigation in
the framework group, and leave the styles import in the local group; you can run
npx eslint --fix to apply auto-fixes or reorder manually ensuring no extra blank
lines inside groups and only one blank line between groups.
| const router = useRouter(); | ||
| const queryClient = useQueryClient(); | ||
| const userNickname = getCookie('userNickname'); | ||
| const prevPath = getStorageValue('prevPage') || ''; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/app/test/result/page.tsx | sed -n '30,50p'Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 940
🏁 Script executed:
cat -n src/app/test/result/page.tsx | sed -n '90,105p'Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 693
🏁 Script executed:
wc -l src/app/test/result/page.tsxRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 100
🏁 Script executed:
rg "getStorageValue" src/ --type ts --type tsx -B 2 -A 2Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 95
🏁 Script executed:
rg "prevPage" src/ --type ts --type tsxRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 95
🏁 Script executed:
fd -e ts -e tsx src/ -x grep -l "getStorageValue"Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 389
🏁 Script executed:
rg "getStorageValue" src/ -B 2 -A 2Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 4828
🏁 Script executed:
rg "prevPage" src/Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 564
🏁 Script executed:
fd . src/ -e ts -x grep -l "getStorageValue" {} \;Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 96
🏁 Script executed:
rg "prevPage" src/app/test/page.tsx -B 5 -A 5Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 453
🏁 Script executed:
cat -n src/app/test/page.tsx | head -50Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 2074
🏁 Script executed:
rg "router.push" src/app/test/result/page.tsx -B 3 -A 3Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 884
🏁 Script executed:
cat -n src/app/test/page.tsx | tail -100Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 3469
🏁 Script executed:
rg "localStorage.setItem\('prevPage'" src/Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 303
🏁 Script executed:
rg "prevPath" src/app/searchResult/SearchResultPageClient.tsx -B 2 -A 2Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 343
🏁 Script executed:
grep -n "router.push(prevPath)" src/app/searchResult/SearchResultPageClient.tsxRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 50
prevPath 기본값을 '/'로 변경하세요
Line 33에서 getStorageValue('prevPage')가 null을 반환하면 기본값 ''이 설정되어 Line 97에서 router.push('')가 호출됩니다. test 페이지로 직접 접근하거나 localStorage가 클리어된 경우 prevPage가 없으므로 기본값을 홈으로 설정해야 합니다.
수정
- const prevPath = getStorageValue('prevPage') || '';
+ const prevPath = getStorageValue('prevPage') || '/';📝 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 prevPath = getStorageValue('prevPage') || ''; | |
| const prevPath = getStorageValue('prevPage') || '/'; |
🤖 Prompt for AI Agents
In `@src/app/test/result/page.tsx` at line 33, Change the default prevPath from an
empty string to '/' so router.push won't be called with an empty path: update
the assignment where prevPath is set (const prevPath =
getStorageValue('prevPage') || '') to use '/' as the fallback; ensure any
subsequent usage such as router.push(prevPath) in the same file (page.tsx) will
correctly navigate to the home page when getStorageValue('prevPage') returns
null/undefined.
| useEffect(() => { | ||
| if (!targetId) return; | ||
|
|
||
| const performScroll = (element: HTMLElement) => { | ||
| element.scrollIntoView({ | ||
| block: 'start', | ||
| behavior: 'smooth', | ||
| }); | ||
|
|
||
| // url에서 파라미터 제거 | ||
| router.replace(pathname, { scroll: false }); | ||
| }; | ||
|
|
||
| // height 생길 때까지 체크 | ||
| const observer = new MutationObserver((_, obs) => { | ||
| const element = document.getElementById(targetId); | ||
| if (element && element.offsetHeight > 0) { | ||
| performScroll(element); | ||
| obs.disconnect(); | ||
| } | ||
| }); | ||
|
|
||
| observer.observe(document.body, { | ||
| childList: true, | ||
| subtree: true, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "useScrollToTarget.ts" | head -20Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 192
🏁 Script executed:
cat -n src/hooks/useScrollToTarget.ts | head -50Repository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 192
🏁 Script executed:
wc -l src/hooks/useScrollToTarget.tsRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 101
🏁 Script executed:
cat src/hooks/useScrollToTarget.tsRepository: JEOLLOGA/JEOLLOGA-CLIENT
Length of output: 1127
이미 렌더된 타겟 요소는 스크롤이 발생하지 않습니다.
MutationObserver는 관찰자가 설정된 이후의 DOM 변경만 감지합니다. 만약 targetId 요소가 useEffect 실행 시점에 이미 DOM에 존재하면 콜백이 실행되지 않아 스크롤이 누락됩니다. 특히 ?scrollTo=id 파라미터를 포함한 URL로 이동할 때 대상 요소가 초기 렌더링 시 이미 있으므로 이 버그가 자주 발생합니다.
🔧 제안 수정
useEffect(() => {
if (!targetId) return;
const performScroll = (element: HTMLElement) => {
element.scrollIntoView({
block: 'start',
behavior: 'smooth',
});
// url에서 파라미터 제거
router.replace(pathname, { scroll: false });
};
+ // 이미 존재하는 요소 먼저 확인
+ const existing = document.getElementById(targetId);
+ if (existing && existing.offsetHeight > 0) {
+ performScroll(existing);
+ return;
+ }
+
// height 생길 때까지 체크
const observer = new MutationObserver((_, obs) => {
const element = document.getElementById(targetId);
if (element && element.offsetHeight > 0) {
performScroll(element);
obs.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
return () => observer.disconnect();
}, [targetId, router, pathname]);🤖 Prompt for AI Agents
In `@src/hooks/useScrollToTarget.ts` around lines 12 - 37, The useScrollToTarget
effect (useScrollToTarget) currently only relies on a MutationObserver so if the
target element already exists at effect run time the scroll is never triggered;
fix by first checking document.getElementById(targetId) immediately and calling
performScroll(element) if found, before instantiating the MutationObserver,
otherwise keep the observer logic as-is; reference performScroll,
MutationObserver instance (observer) and observer.observe to locate where to add
the immediate existence check and early performScroll call.
bykbyk0401
left a comment
There was a problem hiding this comment.
사용자 경험이 잘 고려된 것 같네요우~~
고생하셨습니당
🛰️ 관련 이슈
🧑💻 작업 내용
🗯️ PR 포인트
테스트 결과를 setQueryData로 캐시에만 저장하던 구조라 새로고침 시 결과 데이터가 유실되는 문제가 있어서
결국 어떠한 값이든 저장해둬야 하는 구조라면 불필요한 요청을 줄일 수 있는 2번 방식이 나은 것 같아서 세션에 저장해두고 React Query 캐시가 없을 경우 세션에 저장된 데이터를 사용하도록 했습니다 해당 data는 /test 재진입 시 지워집니다
페이지 이동 후 특정 컴포넌트의 위치로 스크롤을 이동 시키는 useScrollToTarget 훅을 만들었습니다 라우터 넘길 때
scrollTo=targetId쿼리 붙여서 넘겨주면 됩니다 ! 해시도 동작 안하고 앱라우터라 Route Masking도 안돼서 이렇게 함 ..🚀 알게된 점
📖 참고 자료 (선택)
📸 스크린샷 (선택)
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
✏️ Tip: You can customize this high-level summary in your review settings.