Phx-update="ignore" for route changes?

I’m building a liveview application and I’ve run into something I am unsure of the way it is supposed to work.

I am trying to use an external js library, which is just an external script tag put at the end of the body and it finds a div on the page of a certain name and fills it with stuff. This seems like the kind of situation phx-update="ignore" is intended to handle.

As a test example, since the codebase I’m working on is a bit more complicated (but can share if needed), I tried in a brand new Phoenix liveview app using --live to generate it (FYI the latest phx.new generates a liveview 0.17.x not 0.18.x, so I upgraded the test app to 0.18.x). Then I ran a mix phx.gen.live to generate a new set of CRUD live routes and pages. I tried putting the following code into the index.html.heex but didn’t get what was expected on every action.

      <div id="test-ignore" phx-update="ignore">
        This number should never change: <%= Enum.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) %>
      </div>

When the application does a push_patch, it works as expected. When it does push_redirect however, the page doesn’t actually do a redirect but it does change the numbers. Same goes for the script tag, its not listening for the DOM elements it added to be removed and so they disappear even though it was supposed to be ignored.

Is this the expected behavior? Can phx-update="ignore" not function through a push_redirect? If it were actually sending the browser a redirect I would see it on the Network tab of the browser, but that does not happen. Instead it seems like liveview is just rerendering everything and doing a big DOM replacement of the whole page. This breaks the way phx-update="ignore" is supposed to work and makes it seem broken since even when the same page is rendered, the div is not “ignored”.

push_patch is for doing a history.pushState operation in the browser (changing the URL) while staying connected to the same LiveView process. push_redirect has been deprecated in favor of push_navigate and is for changing the URL and switching to a new LiveView process using the existing WebSocket connection.

I’m pretty sure what’s going on here is that since it’s a new LiveView process it’s doing an initial render, rather than rendering the diff since the last render.

Here’s the docs on the deprecated push_redirect/2, which has been replaced by push_navigate/2. It is re-mounting with push_redirect as the docs say:

The current LiveView will be shutdown and a new one will be mounted in its place, without reloading the whole page. This can also be used to remount the same LiveView, in case you want to start fresh. If you want to navigate to the same LiveView without remounting it, use push_patch/2 instead.

It seems like expected behavior as you’re essentially “starting fresh” each time, thus you’re getting another random number because you’re re-mounting a new process. I think?

I would strongly suggest to use js hooks to couple external js to LV lifecycles rather than trying to make LV never update certain dom nodes.

Was attempting to add third party script, so JS hooks aren’t possible as I don’t own the original JS. Only way I could get it to work was to copy in the DOM structure produced by the JS and then let the JS fetch the styling…

React would have a similar problem with these kinds of things, which is why you’d usually reach for the react library version but I’m fairly certain nothing like that exists for most third party scripts for LiveView. I understand why its happening, but it would just be nice to be able to tell LV to just completely cut this DOM element out of diffs regardless of how it gets rendered since that is the expectation with phx-update="ignore".

I don’t think “phx-ignore” will help you much if the JS is that inflexible. The JS will only ever be loaded once, so it will never apply to any elements you add to the DOM later, no matter if they have phx-ignore on them or not. I’ve seen people defer to iframes to make things work with “embed js”, but really to me the issue here is JS libraries, which apply their functionality only on script or page load. They’ll break with any client side rendering done no matter the concrete tooling.

That’s not necessarily true. If the js library allows you to initialize it explicitly via a function, you can use this in the hook of the element:

mounted() {
            ThirdPartyLibraryInitFunction(this.el, ...)
}

You might also need a phx-update=ignore on the element, so that from this point on the element is managed by the 3rd party library without LV interfering. This is the pattern I use to add a working timepicker to my forms.

However, this is not applicable if the JS library is “dumb” and simply looks for a static element in the page. This seems to be your situation, but I wanted to make you aware of this just in case.

1 Like

Yeah, I dumped the library and just did its functionality myself. Was just hoping to avoid that but it didn’t work out. It seems though the phx-update property in this case however, doesn’t follow principle of least surprise when the same element with ignore is rendered it still wipes it out. Nice to have really, but I understand the reason why its happening.