How To Alert A User Before Leaving A Page In React

By Mike Pottebaum

October 26th, 2020

image

This past week I had to figure out how to stop a user before leaving a specific page for broadcasting a concert and, if they choose to close the tab or navigate to a different page, hit an API endpoint that ends the concert.

This was a difficult problem because there are multiple ways a user can leave a single page of a website and they aren’t related. Closing a tab, going to a different URL, or even refreshing the page are different from clicking the HOME or PROFILE buttons.

Furthermore, I wasn’t able to find any way to customize these alerts or hide the default browser messages and create my own. Ultimately, I found that, in React at least, trying to intercept a user before they leave a page is a frustrating undertaking. Proceed with caution.

When I took the ticket to work on this problem, my first thought was to place any logic I needed for this task in the return value of a useEffect callback function like this:

useEffect(() => {
  return () => {
    // hit endpoint to end show
  }
}, [])

The empty array means this returned function will only run when the component unmounts. But, I need to give the user the option to stay on the page. There’s no way to cancel an unmount that I know of. After some digging, I found React Router’s Prompt component.

Prompt component

The Prompt component is a nice component available in the React Router API. All you have to do is tell the Prompt component when to prompt or alert the user and what message to display in the alert. Then, just place the Prompt at the top of your component;

const Component = () => (
  <Container>
    <Prompt
      when={isPrompt()}
      message={() => 'Are you sure you want to leave this page?'}
    />
    <h1>This is a component.</h1>
  </Container>
)

Then, I placed the logic to end the concert in the cleanup function in my useEffect callback like I discussed before:

useEffect(() => {
  return () => handleEndConcert()
}, [])

const handleEndConcert = async () => {
  await fetcher({
    url: endConcert(concert.id),
    method: 'PUT'
  })
}

Unfortunately, there’s no way to customize the Prompt beyond the message content or to hide the alert box and make your own. The most popular solution to this problem I found is to add a click listener to any links/buttons that route to different pages that triggers the custom alert box.

I chose not to do this at this time because that sounds like a lot of work and it still doesn’t cover all of the ways in which a user can leave a page. To intercept a user before they close a tab or go to a different website, I would have to use an entirely different API.

beforeunload and unload events

To detect if a user is closing the tab or navigating to a different website, you have to use some good ol’ vanilla JavaScript. To warn users before closing the tab/window, refreshing the page, or entering a different URL, add an event listener to the window that listens for beforeunload:

useEffect(() => {
  window.addEventListener('beforeunload', alertUser)
  return () => {
    window.removeEventListener('beforeunload', alertUser)
  }
}, [])

const alertUser = e => {
  e.preventDefault()
  e.returnValue = ''
}

In the callback function, alertUser, I’m preventing the default behavior, which in this case would be closing the tab or one of the other ways to leave the page. I’m not sure why setting the returnValue to an empty string is necessary, but it didn’t work before I added that.

This stops the user before closing the tab and displays the default If you leave this page, changes may not be saved message (or something like that) that you’ve probably seen on other websites.

I’ve now warned the user before leaving the page. To run some logic after they choose to leave the page, I need to add another event listener to the window, this time for the unload event:

useEffect(() => {
  window.addEventListener('beforeunload', alertUser)
  window.addEventListener('unload', handleEndConcert)
  return () => {
    window.removeEventListener('beforeunload', alertUser)
    window.removeEventListener('unload', handleEndConcert)
  }
}, [])

const handleEndConcert = async () => {
  await fetcher({
    url: endConcert(concert.id),
    method: 'PUT'
  })
}

If the page does unload, the handleEndConcert function runs.

Including everything together, the very basic Component would look something like this:

const Component = () => {
  useEffect(() => {
    window.addEventListener('beforeunload', alertUser)
    window.addEventListener('unload', handleEndConcert)
    return () => {
      window.removeEventListener('beforeunload', alertUser)
      window.removeEventListener('unload', handleEndConcert)
      handleEndConcert()
    }
  }, [])

  const alertUser = e => {
    e.preventDefault()
    e.returnValue = ''
  }

  const handleEndConcert = async () => {
    await fetcher({
      url: endConcert(concert.id),
      method: 'PUT'
    })
  }

  return (
    <Container>
      <Prompt
        when={isPrompt()}
        message={() => 'Are you sure you want to leave this page?'}
      />
    </Container>
  )
}


Continue Learning