Simple javascript not executing when wrapped in the connected mount

In my LiveViews I’m running into trouble with code that should be very simple:

<script>
alert(" Not connected!");
<%= if Phoenix.LiveView.connected?(@socket) do %>
document.querySelector(".title.main").style.display="block";
document.addEventListener("readystatechange", function(){
    if (document.readyState === "complete") {
    setTimeout(function(){alert("connected!")}, 1000);
    }
})
<%end%>
</script>

Problem: javascript inside the <%= if Phoenix.LiveView.connected?(@socket) do %> is not executed although it is properly mounted in the HTML document (it’s there).
The alert(" Not connected!") gets executed once (I believe it is in the first mount and as it stays untouched on the second mount it seems logic not being executed again). No problem unless my logic is wrong.
document.querySelector(".title.main").style.display="block"; acts on a <h1> that already exists on the first mount and it’s untouched on the second, so no DOM changes and no need for hooks I believe, but still display doesn’t change.
And trying to wrap the alert("connected!") in a document.readyState === "complete" and even a setTimeout() doesn’t work either.
Doesn’t make sense for me as as soon as it is placed in the DOM it should execute.
Can someone explain why this is happening?

1 Like

This is not the right way to interface between Javascript and PhoenixLiveView. You should use a hook instead: https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#module-js-interop-and-client-controlled-dom

OK, I read about the hooks. But I though they should only be used when you were acting on DOM elements life-cycle through LiveView. As that was not the case with a simple alert() function, I didn’t see where that need comes from.
But, thanks, now I will just use Hooks! :slight_smile:

Mounting is part of the life cycle, and while alert doesn’t make DOM changes editing the style block definitely does. The second issue is that your other alert will only be fired after the ready state change event happens. However, I’m pretty sure that the document state is already complete when that javascript is added, so the event never fires.

1 Like

By the way, I highly recommend against making style changes this way. Instead, conditionally apply classes to the dom elements directly:

<section class="title main <%= if connected?(@socket), do: "connected" %>">

Then reference the connected class in your CSS.

2 Likes

That may look a small detail but it’s important for me to understand. And honestly, I though that the second mount would behave like a server rendering, although some elements were not changed.
What you’re telling me is that we should treat it, from this perspective, like a client-side Javascript rendering, ie, no state change is triggered by default.
Summarising, according to you:

  1. first page rendering through HTTP GET - standard server side rendering with first mount call on the Liveview having access to @conn, not @socket;
  2. second mount call on Liveview - client side “javascript like” rendering, no state change triggered, access to @socket inside mount and what is passed again in the web socket (regarding HTML/javascript) is only what is inside the Liveview template, not the layout.
    I also treat this first landing page a little bit special as, by definition, the user could not have interacted with the page so far and as such, LiveViews changes cannot exist.

Is there/are there any good readings about this? For me this is the most important pat to truly understand how Liveview works.

True, the purpose of the code above was to test different approaches at the same time to understand what was happening as I was not getting the picture right.
Thanks!

Yes because it is client side javascript that is ultimately updating the DOM.

Now that is clear to me, thanks.
But if that’s the case why is there a need to re-render HTML that is already in the LiveView in the first mount. I mean, on the first request to a web page, our landing page code is composed by (if only using LiveViews and live routing to “/”):
Root layout (assuming it's used) + live layout + home/landing/index page html.leex.
In this scenario no change is allowed from the user/client side as it is the first time the web page is rendered.
Why is there a need of a second rendering on the second mount? Shouldn’t the first mount on the GET request be the one exactly to be shown with no further interaction from Javascript code from Liveview?
Because we could attach everything we need for the liveview already in the first mount or …?
Assuming no <%= if Phoenix.LiveView.connected?(@socket) do %> exist.

The second render is a complete re-ender including the data fetch part. On render 1, some data is fetched, and the page rendered. On render 2, the data is fetched again and may have changed. Thus the second render totally clobbers the first.

1 Like