const handleItemClick = useCallback((url: string) => {
router.push(url);
handleClose();
}, [router, handleClose]);
handleItemClick함수에 handleClose()를 같이 넣어 해당 url로 이동하면서 사이드네비게이션을 닫도록 시도했지만, Next.js에서 useRouter를 사용할 때 프로그래매틱 네비게이션(router.push)는 클라이언트 사이드 전환을 수행하므로 컴포넌트가 리랜더링 되지 않아 사이드 네비게이션이 닫히지 않음.
useEffect(() => {
handleClose();
}, [pathName, handleClose]);
useEffect(() => {
return () => {
handleClose();
};
}, [pathName, handleClose]);
1) ChatSideNav 컴포넌트에 useEffect를 추가하여 pathName이 변경될 때 사이드 네비게이션을 닫도록 시도.
2) cleanup 함수를 사용하여 컴포넌트가 언마운트 되거나 의존성 배열의 값이 변경될 때 실행되도록, 페이지 이동 시 사이드 네비게이션을 닫는 방법을 시도.
페이지 새로고침 시 메뉴바 버튼이 아예 열리지 않는 문제 발생. 화면이 어두워졌다가 다시 밝아지는 것으로 보아 잠깐 열렸다가 닫히는 과정이 반복되는 것으로 보임. → 비동기 작업으로 인한 오류
실행 순서
useEffect는 렌더링이 완료된 이후에 비동기적으로 실행됨. 이로 인해 다음과 같은 순서로 이벤트가 발생했을 것으로 예상.
a. 메뉴 버튼 클릭
b. isSideNavOpen 상태 true로 변경
c. 컴포넌트 리렌더링(사이드 네비게이션 열림, 배경 어두워짐)
d. 렌더링 완료
e. useEffect 실행 (여기서 handleClose 함수 호출)
f. isSideNavOpen 상태 false로 다시 변경
g. 다시 컴포넌트 리렌더링 (사이드 네비게이션 닫힘, 배경 밝아짐)
상태 업데이트의 비동기성 : React 상태 업데이트는 비동기적으로 처리됨. useEffect내에서 handleClose를 호출하면, 이 상태 업데이트가 즉시 반영되지 않고 다음 렌더 사이클에서 처리됨.
불필요한 상태 변경 : 메뉴 버튼을 클릭하여 사이드 네비게이션을 열려고 했지만, useEffect가 즉시 이를 닫으려고 시도하면서 불필요한 상태변경 발생
사용자 경험 저하 : 이로 인해 사이드 네비게이션이 잠깐 열렸다가 바로 닫히면서 화면이 깜박이는 듯한 효과를 만들어냄
의도하지 않은 동작 : 원래 의도는 페이지 전환 시에만 사이드 네비게이션을 닫는 것이었지만, useEffect가 컴포넌트가 마운트되거나 업데이트될 때마다 실행되어 의도하지 않은 동작을 유발함.
useLayoutEffect(() => {
if (prevPathNameRef.current !== pathName) {
handleClose();
prevPathNameRef.current = pathName;
}
}, [pathName, handleClose]);
useLayoutEffect를 사용하여 동기적으로 실행시켜 DOM 업데이트 직후에 실행되도록 하여 비동기적으로 작동하는 문제를 해결하도록 함.
useRef를 사용하여 이전 pathName을 저장하고, 이를 통해 실제 경로 변경이 있을 때만 handleClose함수를 호출하여 불필요한 함수 호출 방지. 현재 pathName이 이전 pathName과 다를 때만 handleClose함수를 호출하고, 이전 pathName을 업데이트.
"use client";
import { usePathname, useRouter } from "next/navigation";
import { useCallback, useLayoutEffect, useState, useRef } from "react";
import SessionsChat from "./SessionsChat";
import SearchSessions from "./SearchSessions";
interface ChatSideProps {
isSideNavOpen: boolean;
handleClose: () => void;
}
const ChatSideNav = ({ isSideNavOpen, handleClose }: ChatSideProps) => {
const [searchQuery, setSearchQuery] = useState("");
const pathName = usePathname();
const router = useRouter();
const prevPathNameRef = useRef(pathName);
const handleSearch = useCallback((query: string) => {
setSearchQuery(query);
}, []);
const handleItemClick = useCallback(
(url: string) => {
router.push(url);
},
[router]
);
useLayoutEffect(() => {
if (prevPathNameRef.current !== pathName) {
handleClose();
prevPathNameRef.current = pathName;
}
}, [pathName, handleClose]);
return (
<>
{isSideNavOpen && <div className="fixed inset-0 bg-system-black bg-opacity-50 z-40" onClick={handleClose}></div>}
<nav
className={`fixed top-0 left-0 bottom-0 w-72 bg-system-white shadow-lg z-50 transform transition-transform duration-300 ease-in-out ${
isSideNavOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
<div className="p-4">
<SearchSessions handleSearch={handleSearch} initialSearchQuery={searchQuery} />
<SessionsChat
aiType={pathName.includes("assistant") ? "assistant" : "friend"}
searchQuery={searchQuery}
handleItemClick={handleItemClick}
/>
</div>
</nav>
</>
);
};
export default ChatSideNav;