Warn Users Before Exiting: Handling Unsaved Changes in React

In web applications, it’s very frustrating for users to lose their progress due to an accidental page refresh or navigation. As developers, we can implement a simple yet powerful mechanism to prevent such data loss. In this tutorial, I’ll show you how to create an exit prevention system for forms in React that keeps your users’ data safe.

DevRecipes.net - the form we'll be using for Warn Users Before Exiting

Detect form changes

The first step is to detect if the form is touched or not. For this purpose:

  • I’ll add a state variable isDirty
  • attach an input event listener to the form element in an useEffect
  • If there is any input, I’ll set isDirty to true
function Form() {
  const formRef = useRef(null)
  const [isDirty, setIsDirty] = useState(false);

  useEffect(() => {
    const setDirtyState = () => {
      setIsDirty(true);
    }
    formRef?.current?.addEventListener('input', setDirtyState);
    return () => {
      formRef?.current?.removeEventListener('input', setDirtyState);
    }
  }, [])

  return (
      <form ref={formRef} className="form">
        <div>
          <label htmlFor="name">Name</label>
          <input type="text" id="name" />
        </div>
        <div>
          <label htmlFor="email">Email </label>
          <input type="email" id="email" />
        </div>
        <div>
          <label htmlFor="pass">Password </label>
          <input type="password" id="pass" />
        </div>

      </form>
  )

Prevent the user from leaving the page if there are unsaved changes

The next step is to implement an exit prevention mechanism using the beforeunload event. This approach allows us to detect when a user attempts to leave the page and trigger a warning dialog if there are unsaved changes. Here’s how we can intercept and manage page exit attempts in React:

  useEffect(() => {
     // No need to prevent the user from leaving the page, if there are no changes
    if (!isDirty) {
      return;
    }
    const preventLeaving = (e) => {
      e.preventDefault();
    }
    window.addEventListener('beforeunload', preventLeaving);

    // Clean up the event listener to avoid memory leaks
    return () => {
      window.removeEventListener('beforeunload', preventLeaving);
    }
  }, [isDirty])

Now you can see that whenever the user tries to leave the page with unsaved changes, this prompt appears:

Here is the final code:

import { useEffect, useRef, useState } from 'react'
import './Form.css'

function Form() {
  const formRef = useRef(null)
  const [isDirty, setIsDirty] = useState(false);

  useEffect(() => {
    const setDirtyState = () => {
      setIsDirty(true);
    }
    formRef?.current?.addEventListener('input', setDirtyState);
    return () => {
      formRef?.current?.removeEventListener('input', setDirtyState);
    }
  }, [])

  useEffect(() => {
     // No need to prevent the user from leaving the page, if there are no changes
    if (!isDirty) {
      return;
    }
    const preventLeaving = (e) => {
      e.preventDefault();
    }
    window.addEventListener('beforeunload', preventLeaving);

    // Clean up the event listener to avoid memory leaks
    return () => {
      window.removeEventListener('beforeunload', preventLeaving);
    }
  }, [isDirty])

  return (
      <form ref={formRef} className="form">
        <div>
          <label htmlFor="name">Name</label>
          <input type="text" id="name" />
        </div>
        <div>
          <label htmlFor="email">Email </label>
          <input type="email" id="email" />
        </div>
        <div>
          <label htmlFor="pass">Password </label>
          <input type="password" id="pass" />
        </div>

      </form>
  )
}

export default Form

Enjoy!