LiveView updated hook fires every time instead of once

Dear Phoenix users,

I have a Phoenix umbrella project which uses LiveView (Phoenix 1.4.15 and LiveView 0.8.1). It contains a live view which has regular assigns that are updated approximately every 5 seconds and a graph (dygraphs JS) whose data is loaded with the press of a button (and might be updated at later time but is not for the moment).

The problem is that, once the button has been pressed and the data loaded, every time the other assigns are updated, the “updated” hook for the graph is called even if the graph data has not changed.

I made a minimal working example to check the problem without the rest of the code base and it is still present. The frequency of refresh has been set to 1 second. You can see on the screenshot that the Google JS console indicates that the updated message has been printed a total of 36 times instead of just one time, just after the press of the “update” button.

The question is : why does the update of @x (and not @values) leads to a call of the updated hook ?

The problem looks like LiveView updated hook problem but the versions of the software are different. Maybe I should have posted this question as a reply to the previous post even if it doesn’t answer the OP question.

TIA

The relevant edited code is included below.

app.js

let graph = null;
let Hooks = {};
Hooks.chart = {
  mounted() {
    console.log("mounted");
  },
  updated() {
    console.log("updated");
    const data = JSON.parse(this.el.dataset.values);
    if (graph === null) {
      const div = this.el;
      graph = new Dygraph(div, data, {
        labels: ["x", "y"],
        legend: 'always',
        connectSeparatedPoints: true,
        labelsDiv: "legend",
      });
    } else {
      graph.updateOptions({ 'file': data });
    }
  }
}
...
let liveSocket = new LiveSocket("/live", Socket, { hooks: Hooks, params: { _csrf_token: csrfToken } });
...

Module PhxLv1Web.TestLive (file test_live.ex)

def render(assigns) do
  Phoenix.View.render(PhxLv1Web.TestView, "test.html", assigns)
end

def mount(_params, _session, socket) do
  if connected?(socket) do
    Process.send_after(self(), :update, 1000)
  end
  {:ok, assign(socket, x: 0, values: nil)}
end

def handle_event("update", _value, socket) do
  IO.puts "update"
  values = (1..100) |> Enum.map(fn i -> [i, :rand.uniform(100)] end)
  {:noreply, assign(socket, :values, values)}
end

def handle_info(:update, %{assigns: %{x: x}} = socket) do
  Process.send_after(self(), :update, 1000)
  {:noreply, assign(socket, :x, x+1)}
end

Template test.html.leex

<p>x = <%= @x %>.</p>
<button phx-click="update">update</button>
<div id="legend"></div>
<div id="graph" phx-update="ignore" phx-hook="chart" data-values="<%= Jason.encode!(@values) %>"></div>

Try master. We are constantly providing enhanceents and bug fixes and this should have been solved. Make sure to remove assets/node_modules after upgrading. If the behaviour persists, please file a bug report.

3 Likes

From 0.8.0, Liveview added css loading classes that are applied phx-bound elements. See Loading state and errors.

EDIT 1: I’ve tested this on master, but it still has the same behaviour.

EDIT 2: If you comment out the logic inside updated, it won’t get called again; somehow, the Dygraph JS library creates this loop; indeed, this was created by the JS library itself; by default, it was redrawing itself every so often => thus the DOM was updated => updated hook was called correctly

{boolean} block_redraw
    Usually the chart is redrawn after every call to updateOptions(). If you know better, you can pass true to explicitly block the redraw. This can be useful for chaining updateOptions() calls, avoiding the occasional infinite loop and preventing redraws when it's not necessary (e.g. when updating a callback). 

http://dygraphs.com/jsdoc/symbols/Dygraph.html

This has been fixed on master. For phx-ignore’d elements, we were invoking the updated hooks because as you figured out we detected the DOM change, but this should only happen if we detect a change in the data attributes on the hook element, so on master we now only invoke if the dataset has changed for an ignored element. Thanks!

3 Likes

Thanks for the suggestions. I will test it with master. And also try to understand the answer by @sfusato suggesting that dygraphs itself creates the loop which is strange since the updated loop frequency is 1Hz, just like the updates of @x themselves, not faster.

I confirm that it is working with master. Another minimal working example has been made to verify it.

Doing mix deps, we see:

* phoenix_pubsub 2.0.0-dev (https://github.com/phoenixframework/phoenix_pubsub.git) (mix)
  locked at 325abd4
  ok
* phoenix 1.5.0-dev (https://github.com/phoenixframework/phoenix.git) (mix)
  locked at dcea155
  ok
* phoenix_live_view 0.9.0 (https://github.com/phoenixframework/phoenix_live_view.git) (mix)
  locked at a8bf8af
  ok

In the deps part of mix.exs, I wrote:

{:phoenix, github: "phoenixframework/phoenix", override: true},
{:phoenix_live_view, github: "phoenixframework/phoenix_live_view"}

The override was necessary because it looks like the dependency specifications provided for Phoenix and for Phoenix LiveView are not coherent and cannot coexist. Maybe I should have written the deps part differently.