KooKs

useNavigationLock

페이지 이탈 시 사용자에게 경고를 표시하는 커스텀 훅
Next.jsReactTypescript

예시

페이지 이탈이 허용됩니다.

API

useNavigationLock()
  • Parameters
    • shouldBlock (boolean)이탈 경고를 활성화할지 여부 (기본값: true)
  • Returns
    • void (void)이 훅은 값을 반환하지 않습니다.

개발 및 사용환경

Next.jsReactTypescript

사용법

1// 폼이 수정되었을 때 이탈 방지
2const [isFormDirty, setIsFormDirty] = useState(false);
3useNavigationLock(isFormDirty);
4
5// 특정 조건에서만 이탈 방지
6useNavigationLock(hasUnsavedChanges);
7
8// 이탈 방지 비활성화
9useNavigationLock(false);

Hook

1import { useRouter, usePathname } from "next/navigation";
2import { useEffect, useCallback } from "react";
3
4/**
5 * 페이지 이탈 시 사용자에게 경고를 표시하는 Hook
6 *
7 * @remarks
8 * - Chrome, Firefox, Safari, Edge 등 모던 브라우저에서 지원됩니다.
9 * - 모바일 브라우저에서는 동작이 제한적일 수 있습니다.
10 * - beforeunload 이벤트를 사용하므로, 실제 메시지 내용은 브라우저마다 다르게 표시될 수 있습니다.
11 *
12 * @param shouldBlock - 이탈 경고를 활성화할지 여부 (기본값: true)
13 */
14export function useNavigationLock(shouldBlock: boolean = true) {
15  const router = useRouter();
16  const pathname = usePathname();
17
18  const handleBeforeUnload = useCallback(
19    (event: BeforeUnloadEvent) => {
20      if (shouldBlock) {
21        event.preventDefault();
22        event.returnValue = "";
23      }
24    },
25    [shouldBlock]
26  );
27
28  useEffect(() => {
29    if (!shouldBlock) return;
30
31    // 브라우저 새로고침/닫기 방지
32    window.addEventListener("beforeunload", handleBeforeUnload);
33
34    // 뒤로가기/앞으로가기 방지
35    const handlePopState = (e: PopStateEvent) => {
36      if (shouldBlock) {
37        e.preventDefault();
38        window.history.pushState(null, "", window.location.href);
39        if (
40          window.confirm(
41            "저장되지 않은 변경사항이 있습니다. 정말로 나가시겠습니까?"
42          )
43        ) {
44          window.history.back();
45        }
46      }
47    };
48
49    // 클라이언트 사이드 네비게이션 방지
50    const handleClick = (e: MouseEvent) => {
51      const target = e.target as HTMLElement;
52      const anchor = target.closest("a");
53
54      if (
55        anchor &&
56        anchor.href &&
57        !anchor.href.startsWith("javascript:") &&
58        anchor.href !== window.location.href
59      ) {
60        e.preventDefault();
61        if (
62          window.confirm(
63            "저장되지 않은 변경사항이 있습니다. 정말로 나가시겠습니까?"
64          )
65        ) {
66          router.push(anchor.href);
67        }
68      }
69    };
70
71    window.addEventListener("popstate", handlePopState);
72    document.addEventListener("click", handleClick);
73
74    // 초기 히스토리 상태 추가
75    window.history.pushState(null, "", window.location.href);
76
77    return () => {
78      window.removeEventListener("beforeunload", handleBeforeUnload);
79      window.removeEventListener("popstate", handlePopState);
80      document.removeEventListener("click", handleClick);
81    };
82  }, [shouldBlock, router, handleBeforeUnload]);
83}