사이드바의 너비를 드래그로 조정하는 기능을 구현 중이였습니다. 재사용을 위해 Hook으로 분리했고, 페이지 이동에도 너비가 변하지 않게 로컬스토리지에 너비를 저장해놓고 불러와서 사용하는 방식으로 구현했습니다.
export const useResizableSidebar = ({
defaultWidth = 412,
minWidth = 300,
maxWidth = 800,
storageKey = "sidebar-width",
}: UseResizableSidebarOptions = {}) => {
const [width, setWidth] = useState(getInitialWidth);
const [isResizing, setIsResizing] = useState(false);
// 문제: width가 변경될 때마다 localStorage에 쓰기
useEffect(() => {
try {
localStorage.setItem(storageKey, width.toString());
} catch (error) {
console.error("Failed to save sidebar width to localStorage:", error);
}
}, [width, storageKey]);
// ... 나머지 코드
};
사이드 바를 드래그하면서 로컬스토리지를 확인해보니, 값이 아주 빠르게 변하는 것을 확인했습니다. useEffect로 너비가 변할때마다 localStorage를 업데이트 하다 보니 발생한 일이였고, 너무 과도한 호출이라는 생각이 들었습니다.
mousemove 이벤트가 초당 수십~수백 번 발생setWidth가 호출되고, 이에 따라 localStorage 쓰기가 발생마우스 이동 → setWidth 호출 → 리렌더링 → useEffect 실행 → localStorage 쓰기
(이 과정이 초당 수십 번 반복!)
드래그하는 동안에는 localStorage에 저장하지 않고, 드래그가 끝났을 때만 저장합니다.
export const useResizableSidebar = ({
defaultWidth = 412,
minWidth = 300,
maxWidth = 800,
storageKey = "sidebar-width",
}: UseResizableSidebarOptions = {}) => {
const [width, setWidth] = useState(defaultWidth);
const [isResizing, setIsResizing] = useState(false);
const startXRef = useRef(0);
const startWidthRef = useRef(0);
useEffect(() => {
try {
const saved = localStorage.getItem(storageKey);
if (saved) {
const parsedWidth = parseInt(saved, 10);
if (parsedWidth >= minWidth && parsedWidth <= maxWidth) {
setWidth(parsedWidth);
}
}
} catch (error) {
console.error("Failed to load sidebar width from localStorage:", error);
}
}, [storageKey, minWidth, maxWidth]);
const handleMouseDown = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
setIsResizing(true);
startXRef.current = e.clientX;
startWidthRef.current = width;
},
[width]
);
useEffect(() => {
if (!isResizing) return;
const handleMouseMove = (e: MouseEvent) => {
const diff = startXRef.current - e.clientX;
const newWidth = startWidthRef.current + diff;
if (newWidth >= minWidth && newWidth <= maxWidth) {
setWidth(newWidth);
}
};
const handleMouseUp = () => {
setIsResizing(false);
// 드래그 종료 시에만 localStorage에 저장
try {
localStorage.setItem(storageKey, width.toString());
} catch (error) {
console.error("Failed to save sidebar width to localStorage:", error);
}
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [isResizing, minWidth, maxWidth, width, storageKey]);
return {
width,
isResizing,
handleMouseDown,
};
};
localStorage 저장 시점 변경
Before
useEffect(() => {
localStorage.setItem(storageKey, width.toString());
}, [width]); // width 변경마다 실행
After
const handleMouseUp = () => {
setIsResizing(false);
localStorage.setItem(storageKey, width.toString()); // mouseup 시 한 번만 실행
};