이전에는 사용자가 게시글을 클릭하면 useState
로 모달 시트를 제어하고, 선택된 게시글 데이터는 props로 전달했습니다.
하지만 이 방식에는 몇 가지 문제가 있었습니다:
아래 두 가지 요구사항을 기준으로 설계했습니다.
postId
를 포함한 URL로 직접 접근 할 수 있어야 한다.사용자가 게시물을 클릭해도 스크롤이 초기화되지 않고, 상세 내용을 모달로 띄운 뒤 닫았을 때 다시 원래 위치로 돌아오게 만들고 싶었습니다.
이를 위해 :postId
파라미터를 중첩 라우팅 구조로 설정하고, <Outlet />
을 활용해 기존 페이지 위에 모달 시트를 오버레이하는 방식으로 설계했습니다.
// app.tsx
<Route path="/mypage" element={<MyPage />}>
<Route path=":postId" element={<CardDetailModal />} />
</Route>
// MyPage.tsx
export default function MyPage() {
return (
<div>
<MyProfileSection />
<Outlet />
</div>
);
}
state
를 활용한 스크롤 이동 제어React Router는 페이지 전환 시 스크롤을 자동으로 맨 위로 올려주지 않기 때문에, 이를 위해 ScrollToTop
컴포넌트를 사용해 스크롤을 수동으로 관리하고 있었습니다.
function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo({
top: 0,
});
}, [pathname]);
return null;
}
/mypage
에서 /mypage/:id
로 이동시 모달은 기존 페이지 위에 띄워지지만, React Router는 페이지 이동으로 간주해 스크롤을 맨 위로 올리는 문제점이 있었습니다.
이를 React Router의 state
값을 활용해 스크롤을 제어했습니다.
모달 페이지로 이동할 때 navigate
, Link
태그를 사용하는 경우 다음과 같이 state
값을 전달했습니다.
// navigate를 사용하는 경우
navigate(`${basePath}/${record.recordId}`, {
state: { scrollLock: true },
});
// Link 태그를 사용하는 경우
<Link
to={newPath}
state={{ scrollLock: true }}
>
그리고 ScrollToTop
컴포넌트에서는 useLocation
을 통해 전달된 state
값을 활용하여, scrollLock
이 true
인 경우에는 스크롤을 수행하지 않도록 설정했습니다.
function ScrollToTop() {
const { pathname, state } = useLocation();
useEffect(() => {
if (state?.scrollLock) return; // scrollLock이 true면 스크롤 안 함
window.scrollTo({
top: 0,
});
}, [pathname]);
return null;
}
이렇게 하면 모달 열기/닫기 시에는 스크롤이 유지되고, 그 외의 일반적인 페이지 이동에서는 정상적으로 스크롤이 맨 위로 이동되도록 처리할 수 있습니다.
URL 파라미터(postId) 기반으로 데이터 페칭하도록 바꿔, 새로고침 및 직접 접근이 가능해졌습니다.
또한 React Router의 중첩 라우팅과 state
를 활용해 모달 시트를 오버레이로 렌더링하고,열림/닫힘·뒤로가기 시 스크롤 위치를 유지하도록 설계했습니다.