Next.js Discord

Discord Forum

Before Unload event BUT .... for the app router

Answered
Giant panda posted this in #help-forum
Open in Discord
Giant pandaOP
Hi folks,
Just wanted to know how do you handle "before unload" event with the app router ?

At the moment, when use window.addEventListener("beforeUnload") it will triggers the browser popup when the user wants to close the tab or try to refresh the current tab (which is correct)
But for the internal routing of an app made with Next, we have no way to detect if the route IS GOING to change and block the router event

What hacks do you use to make sure that before the user change the current page internally (with Link for example) ?

Thanks!
Answered by Giant panda
Ok I think I have found a solution.
My solution was to create a component that would replace my all the currents <Link /> components to override it.

The idea is that as soon as one of my forms is considered dirty, a parameter is added to the url.
For example hasUnsavedChanges=true.

  const form = useForm() // An example with React Hook Form
  const router = useRouter();

  useEffect(() => {
    if (!form.formState.isDirty) return; //
    router.push("?hasUnsavedChanges=true");
  }, [form.formState.isDirty]);



The new <Link/> component monitors the parameter in the URL using the useSearchParams hook, and depending on this boolean will allow the link to continue its routing or not.
"use client";
type Props = ComponentProps<typeof Link>;

const LinkWithBeforeUnload = (props: Props) => {
  const searchParams = useSearchParams();
  const hasUnsavedChanges = searchParams.get("hasUnsavedChanges") === "true";

  const onClick = (e: MouseEvent<HTMLAnchorElement, globalThis.MouseEvent>) => {
    if (!hasUnsavedChanges) {
      return;
    }
    const shouldContinue = window.confirm(
      "You have unsaved changes, are you sure you want to leave this page?"
    );
    if (!shouldContinue) {
      e.preventDefault();
    }
  };

  return (
    <Link {...props} onClick={onClick}>
      {props.children}
    </Link>
  );
};

export default LinkWithBeforeUnload;


The implementation is very basic for this solution, so if you've got another way, I'd be happy to take it, but for the time being I'll go with this and I have just replaced all the imports from next/link to the current path of LinkWithBeforeUnload

⚠️ It's a client component only don't forget to add use client; directive
View full answer

1 Reply

Giant pandaOP
Ok I think I have found a solution.
My solution was to create a component that would replace my all the currents <Link /> components to override it.

The idea is that as soon as one of my forms is considered dirty, a parameter is added to the url.
For example hasUnsavedChanges=true.

  const form = useForm() // An example with React Hook Form
  const router = useRouter();

  useEffect(() => {
    if (!form.formState.isDirty) return; //
    router.push("?hasUnsavedChanges=true");
  }, [form.formState.isDirty]);



The new <Link/> component monitors the parameter in the URL using the useSearchParams hook, and depending on this boolean will allow the link to continue its routing or not.
"use client";
type Props = ComponentProps<typeof Link>;

const LinkWithBeforeUnload = (props: Props) => {
  const searchParams = useSearchParams();
  const hasUnsavedChanges = searchParams.get("hasUnsavedChanges") === "true";

  const onClick = (e: MouseEvent<HTMLAnchorElement, globalThis.MouseEvent>) => {
    if (!hasUnsavedChanges) {
      return;
    }
    const shouldContinue = window.confirm(
      "You have unsaved changes, are you sure you want to leave this page?"
    );
    if (!shouldContinue) {
      e.preventDefault();
    }
  };

  return (
    <Link {...props} onClick={onClick}>
      {props.children}
    </Link>
  );
};

export default LinkWithBeforeUnload;


The implementation is very basic for this solution, so if you've got another way, I'd be happy to take it, but for the time being I'll go with this and I have just replaced all the imports from next/link to the current path of LinkWithBeforeUnload

⚠️ It's a client component only don't forget to add use client; directive
Answer