Next.js에서 layout.tsx
를 사용하여 마이페이지 레이아웃을 사용하다가 문제가 발생했습니다.
이름 옆에 있는 수정 버튼을 클릭하면 다음과 같이 layout의 {children}
부분은 변하지만, layout 에서 컨트롤하고있는 사이드바의 active menu
상태는 변하지 않는 문제였습니다.
제가 기대한대로라면, 이렇게 되었어야 합니다.
- 사이드 메뉴의 active 표시(색상 있는 부분)가 “프로필 수정”으로 변경
- 페이지 subtitle도 “프로필 수정”으로 변경
layout.tsx
코드
const [activeMenu, setActiveMenu] = useState('');
return(
<MainLayout
title={'마이 페이지'}
subtitle={activeMenu}
sidebar={<MypageSidebar activeMenu={activeMenu} setActiveMenu={setActiveMenu} />}
>
{children}
</MainLayout>
)
사이드 메뉴와 수정 아이콘 모두 useRouter로 페이지 이동을 하지만, 왜 사이드 메뉴 클릭 시에는 잘 동작하는데 수정 아이콘 클릭 시에는 안 될까요?
그 이유는 바로 Next.js의 Layout은 라우트 이동 시에도 재마운트되지 않기 때문입니다.
즉, layout.tsx 안에서 useState로 관리하는 값은 페이지가 바뀌어도 그대로 유지됩니다.
- 사이드 메뉴 클릭 시에는 setActiveMenu가 직접 실행되므로 값이 업데이트됨.
- 반면, 수정 아이콘을 통해 라우트만 이동하면 setActiveMenu는 실행되지 않아서 이전 상태가 그대로 유지되는 것.
activeMenu는 URL(pathname)에서 파생되는 값인데 굳이 상태로 들고 있었던 게 문제였습니다.
useState 대신 usePathname()을 사용해서 activeMenu를 매번 계산하도록 수정하였습니다.
중복되는 로직을 하나로 합치기 위해 getActiveMenuName
함수를 생성했습니다. 매개변수로 받은 menu 목록에서 현재 pathname과 같은 것을 찾아서, name을 반환합니다.
function getActiveMenuName(menus: Menu[], pathname: string) {
return (
menus.find(
(m) => pathname === m.path || pathname.startsWith(m.path + '/')
)?.name ?? ''
);
}
const layout = ({ children }: { children: React.ReactNode }) => {
const pathname = usePathname();
const menus = createMypageMenus();
const activeMenu = getActiveMenuName({ menus, pathname });
return (
<MainLayout
title={'마이 페이지'}
subtitle={activeMenu}
sidebar={<SideCategory menus={menus} activeMenu={activeMenu} />}
>
{children}
</MainLayout>
);
};
즉, 코드를 activeMenu를 state대신 usePathname()으로 가져오는 방식으로 수정했으며 그 과정에서 useEffect와 useState 사용을 줄일 수 있었습니다.
처음엔 개발 초기라 별 고민 없이 Sidemenu 컴포넌트에서 클릭한 메뉴를 activeMenu 상태로 들고 관리했는데,
곰곰이 생각해보니 이 값은 URL에서 바로 파생되는 거라 usePathname으로 처리하는 게 훨씬 자연스러운 방식이긴합니다. 아마 나중에 다른 페이지를 개발하면서 자연스럽게 리팩토링 했을 것 같은 부분입니다.
하지만 만약 처음부터 usePathname을 썼다면
이번처럼 layout.ts가 라우트 이동 시 재렌더링되지 않는다는 사실은 놓쳤을 수도 있겠죠?!!
개발을 하다 보면 “이렇게 하는 게 왜 더 좋은지” 를 설명하는 것보다,
오히려 “이렇게 하면 왜 안 되는지” 를 이야기하는 경우가 훨씬 많은 것 같습니다.
어떤 로직을 구현하는 방법은 수없이 많지만,안 되는 케이스는 늘 명확하니까요.
아무튼 그래서 이번 경험은 여러의미로 좋은 실수였습니다 (?)
(합리화 아님)