Cùng viết một custom hook để handle sự kiện rời trang khi đã sửa nội dung của trang trước đó <3

Để nhận biết khi người dùng rời khỏi trang web, chúng ta có thể sử dụng sự kiện beforeunload. Sự kiện này được kích hoạt khi trình duyệt chuẩn bị để thoát khỏi trang web, chẳng hạn như khi người dùng nhấn nút Back, đóng tab hoặc tắt hẳn trình duyệt đi.

Để sử dụng sự kiện beforeunload, chúng ta có thể đính kèm một hàm xử lý vào sự kiện này bằng cách sử dụng phương thức addEventListener trên đối tượng window. Ví dụ:

window.addEventListener('beforeunload', function(event) {
  // Do something here
});


Trong hàm xử lý này, chúng ta có thể thực hiện các thao tác cần thiết, chẳng hạn như hiển thị thông báo xác nhận rời khỏi trang web. Nếu hàm xử lý trả về một chuỗi, trình duyệt sẽ hiển thị thông báo xác nhận với nội dung là chuỗi này, để người dùng xác nhận rằng họ muốn rời khỏi trang web.


window.addEventListener('beforeunload', function(event) {
  event.preventDefault();
  event.returnValue = ''; // Chrome requires this
});


Trong hàm xử lý này, chúng ta gọi phương thức preventDefault() để ngăn chặn trình duyệt thoát khỏi trang web ngay lập tức, và thiết lập thuộc tính returnValue để xác định nội dung của thông báo xác nhận (trong trường hợp trình duyệt yêu cầu). Trong trường hợp này, thông báo xác nhận sẽ không có nội dung, nhưng người dùng vẫn sẽ phải xác nhận rằng họ muốn rời khỏi trang web.


Sử dụng sự kiện beforeunload trong React app


Như vậy anh em đã biết dùng event nào để bắt được sự kiện trước khi rời khỏi trang web. Vậy chúng ta sẽ áp dụng nó như thế nào trong React app ???

Ta sẽ tạo một custom hook gọi là useLeavePageConfirm để tiện dùng cho những component sau này.

const confirmationMessage = "You have unsaved changes. Continue?";

const useLeavePageConfirm = (hasUnsavedChanges: boolean) => {
    React.useEffect(() => {
        const handleBeforeUnload = (event: BeforeUnloadEvent) => {
            if (!hasUnsavedChanges) return;
	        event.preventDefault();
	        return (event.returnValue = confirmationMessage );
        };

        window.addEventListener("beforeunload", handleBeforeUnload);
        return () =>window.removeEventListener("beforeunload", handleBeforeUnload);
    }, [hasUnsavedChanges]);
};


Anh em cùng xem cách sử dụng hook này trong ví dụ với một trang nhập thông tin form như sau:


import { useState } from "react";

const Form = () => {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);

  const handleNameChange = (event) => {
    setName(event.target.value);
    setHasUnsavedChanges(true);
  };

  const handleEmailChange = (event) => {
    setEmail(event.target.value);
    setHasUnsavedChanges(true);
  };

  const handleFormSubmit = (event) => {
    event.preventDefault();
    // save form data heresetHasUnsavedChanges(false);
  };

  return (
    <form onSubmit={handleFormSubmit}><label>
        Name:
        <input type="text" value={name} onChange={handleNameChange} /></label><br /><label>
        Email:
        <input type="email" value={email} onChange={handleEmailChange} /></label><br /><button type="submit">Save</button></form>
  );
};


Trong ví dụ này, chúng ta sử dụng useState hook để định nghĩa trạng thái hasUnsavedChanges. Khi người dùng thay đổi nội dung của form, chúng ta sẽ đặt giá trị của hasUnsavedChanges thành true.

Khi người dùng nhấn nút "Save" để lưu lại form, chúng ta sẽ lưu trữ dữ liệu và đặt giá trị của hasUnsavedChanges thành false.

Sau đó, để sử dụng useLeavePageConfirm hook cho form này, bạn có thể import hook vào component của mình và sử dụng như sau:

import useLeavePageConfirm from "./useLeavePageConfirm";

const Form = () => {
  // ...useLeavePageConfirm(hasUnsavedChanges);

  return (
    // ...
  );
};


Với cách này, hook sẽ được gọi mỗi khi giá trị của hasUnsavedChanges thay đổi. Nếu giá trị này là true, hook sẽ hiển thị một hộp thoại xác nhận khi người dùng muốn rời khỏi trang.

Với useLeavePageConfirm hook, bạn có thể đảm bảo rằng người dùng sẽ không mất đi những thay đổi quan trọng của họ khi rời trang hiện tại do click nhầm vào tắt trình duyệt hoặc tab.


Sử dụng sự kiện beforeunload trong Next app


Anh em đều biết là Nextjs dạo gần đây khá là hot và nó là một framework của React vậy liệu chúng ta có thể sử dụng hook trên trong Nextjs App được không ?

Câu trả lời là có nhưng chưa đủ, vì nếu anh em chỉ sử dụng sự kiện `beforeunload` trong Nextjs thì chỉ bắt được trường hợp đóng tab hoặc tắt trình duyệt thôi còn với các trường hợp như nhấn nút Back hay navigate các page trong Next thì lại không được.

Nhưng rất may là trong router của Next lại có sự kiện `routeChangeStart` có thể handle được việc điều hướng giữa các trang.


Cụ thể thì routeChangeStart là một trong các sự kiện mà Next.js cung cấp để cho phép lập trình viên can thiệp vào quá trình điều hướng của trang web. Nó được kích hoạt khi người dùng bắt đầu chuyển đổi giữa các trang trong ứng dụng của bạn.

Khi sự kiện routeChangeStart được kích hoạt, bạn có thể thực hiện một số tác vụ nhất định trước khi trang mới được tải lên, chẳng hạn như hiển thị một tiêu đề hoặc biểu tượng tải để thông báo cho người dùng rằng ứng dụng đang tiếp tục xử lý yêu cầu của họ.


Vậy giờ ta sẽ sửa lại hook useLeavePageConfirm một chút để nó có thể hoạt động với Nextjs app.

const confirmationMessage = "You have unsaved changes. Continue?";

const useLeavePageConfirm = (hasUnsavedChanges: boolean) => {
    React.useEffect(() => {
        const handleBeforeUnload = (event: BeforeUnloadEvent) => {
            if (!hasUnsavedChanges) return;
	        event.preventDefault();
	        return (event.returnValue = confirmationMessage );
        };
        
        const handleNavigateRoute = () => {
	        if (!hasUnsavedChanges) return;
	        if (window.confirm(warningText)) return;
	        router.events.emit("routeChangeError");
	        throw "routeChange aborted.";
	    };
  
          window.addEventListener("beforeunload", handleBeforeUnload);
          // handle navigations from one page to another
	      router.events.on("routeChangeStart", handleNavigateRoute);
          return () =>
              window.removeEventListener("beforeunload", handleBeforeUnload);
              router.events.off("routeChangeStart", handleNavigateRoute);
      }, [hasUnsavedChanges]);
  };


Kết luận


Tóm lại, useLeavePageConfirm hook là một công cụ hữu ích để xác nhận việc rời khỏi trang và tránh mất mát dữ liệu. Bằng cách sử dụng useEffect hook để theo dõi các thay đổi của form, chúng ta có thể giảm thiểu sự phiền toái khi hiển thị thông báo xác nhận khi người dùng thực sự đã thay đổi nội dung của trang.