Phoenix_live_view: Canceling a `beforeunload` prompt invokes `mount/2`

Created on 5 Jan 2020  路  7Comments  路  Source: phoenixframework/phoenix_live_view

Environment

  • Elixir version (elixir -v): Elixir 1.9.4 (compiled with Erlang/OTP 22)
  • Phoenix version (mix deps): 1.4.11
  • Phoenix LiveView version (mix deps): 0.4.1 locked at 8ff349e
  • NodeJS version (node -v): v13.5.0
  • NPM version (npm -v): 6.13.4
  • Operating system: GNU/Linux

Actual behavior

We're trying to implement a "Leave site? Changes you made may not be saved." prompt. Everything is working nicely with these changes:

app.js

let Hooks = {};

Hooks.BeforeUnload = {
  hasChanged() {
    return this.el.dataset.hasChanged === "true";
  },
  mounted() {
    window.addEventListener("beforeunload", e => {
      if (this.hasChanged()) {
        e.preventDefault();
        e.returnValue = "";
      }
    });
  }
};

let liveSocket = new LiveSocket("/live", Socket, { hooks: Hooks });

form.html.leex

<%= f =
      form_for(@changeset, "#",
        phx_change: :changed,
        phx_submit: :submitted,
        phx_hook: "BeforeUnload",
        data: [has_changed: !Enum.empty?(@changeset.changes)]
      ) %>

However, when the user cancels the prompt, mount/2 is invoked and the form changes get discarded, which defeats the purpose of the prompt.

The logs show this after canceling the prompt:

[info] CONNECTED TO Phoenix.LiveView.Socket in 260碌s

Expected behavior

When the user cancels the prompt, unload event is canceled and the form changes are kept.

All 7 comments

Here's a sample repo that reproduces the issue: https://github.com/aptinio/phx_lv_550. I hope this helps.

it happens because Phoenix Socket closes the websocket connection even though the User stays in the page clicking on the cancel button:

https://github.com/phoenixframework/phoenix/blob/b93fa36f040e4d0444df03b6b8d17f4902f4a9d0/assets/js/phoenix.js#L768

I've done some tests and removing that line I got the Hooks.BeforeUnload working as expected. In order to fix that, I think we should somehow check if the user stayed on the page - when he clicks on the cancel button - and don't close the connection.

What about if we use unload instead of beforeunload in order to close the connection? :thinking:I tested it here and it also works, but I'm not sure if that's ok since I'm not familiar with those events.

@chrismccord you might have some thoughts on this since you've introduced this approach on Phoenix channel :raised_hands: :

What about if we use unload instead of beforeunload in order to close the connection?

I thought this doesn't work until I realized I was changing phoenix_live_view.js instead of phoenix.js. :sweat_smile:

I think using unload makes sense. According to MDN:

It is fired after:

  • beforeunload (cancelable event)
  • pagehide

The document is in the following state:

  • All the resources still exist (img, iframe etc.)
  • Nothing is visible anymore to the end user
  • UI interactions are ineffective (window.open, alert, confirm, etc.)
  • An error won't stop the unloading workflow

unload works, but I need to release a new phoenix to make it happen, so stand by for Phoenix v1.4.12 sometime tomorrow. Thanks!

Awesome! Thanks @feliperenan and @chrismccord! :heart:

This has been fixed in Phoenix 1.4.12 and LV 0.6.0. Thanks!

Was this page helpful?
0 / 5 - 0 ratings