I’m struggling with something that seems like it should be simple. I would like to fetch some data via async when the page loads, and then once loaded, graph that data with Highcharts. Everything I’ve read has indicated that you need to setup a hook that will allow you to pass the data from the server to the JS, and you use a handle_event to trigger push_event() with the data. I don’t have a user interaction to trigger the handle_event, I just want the graph to load once the data is ready. I tried adding the push_event() to handle_async , but nothing happens in the JS hook. I have added a dbg() line to verify that the data is being received in the handle_async.
What am I missing?
Some code:
def mount() do
...
|> start_async(:points, fn -> Tokens.generate_chart_data() end)
...
end
def handle_async(:points, {:ok, points}, socket) do
dbg(points)
{:noreply, push_event(socket, :points, %{points: points})}
end
def render(assigns) do
~H"""
...
<div class="mt-4 flex flex-row w-full">
<div id="graph" phx-hook="Graph"></div>
</div>
"""
end
Firstly, you need to put this.handleEvent("points", ({points}) => logToConsole(points); in mounted.
You can set it up so that the process receives a message every x seconds, which you handle in handle_info and can trigger the start_async from there.
You can send the message every x seconds with :erlang.send_interval(Time, Destination, Message) or trigger it each time you process the message in handle_info/handle_async. That is Process.send_after(...)), called once in mount and then again in handle_info
If the data is on some publication you could subscribe to that in your liveview.
Hello! Thanks for that info. Do I need this.handleEvent in both mounted() and updated()? Or just in mounted()?
The data is static, on my local server. Basically just a bunch of numbers and timestamps that are retrieved, compiled and sent to the front end. So once the data has been retrieved, it won’t change.
Sorry, I’m new to Elixir & Phoenix, where in the liveview would I setup the :erlang.send_interval()?
Update: I got it working without hooks! It was as straight forward as it seemed like it should be, I just didn’t know what I didn’t know. The final working product:
live view:
def mount(params, _session, socket) do
...
socket =
socket
...
|> start_async(:points, fn -> Tokens.generate_chart_data() end)
{:ok, socket}
end
def handle_async(:points, {:ok, points}, socket) do
{:noreply, push_event(socket, "graph", %{points: points})}
end
def render(assigns) do
~H"""
...
<div id="graph"></div>
"""
end
app.js:
window.addEventListener(
"phx:graph",
e => {
if (e.detail?.points) {
Highcharts.chart('graph', { ... });
}
}
)
Ok, that makes more sense w/the timer stuff. I was wondering, but figured I’d give it a try just to see if I could get ANYTHING to work
I’m still unclear as to how I would implement this with hooks as there’s no user interaction
To do the initial one, you push an event in mount if it is a liveview or in update if it is a live component.
For example:
defmodule MyWeb.Components.Charts.EchartComponent do
use MyWeb, :surface_live_component
@doc "E-Charts `option` configuration map"
prop option, :map, required: true
@doc "Merge the option with the existing one (`true`), or replace it (`false`)?"
prop merge, :boolean, default: true
prop class, :css_class, default: "flex h-full w-full"
def push_loading_event(socket, chart_id) when is_binary(chart_id) do
push_event(socket, "echarts:#{chart_id}:loading", %{})
end
def push_update_event(socket, chart_id, %{"option" => _} = params) when is_binary(chart_id) do
params = Map.put_new(params, "merge", true)
push_event(socket, "echarts:#{chart_id}:update", params)
end
def push_init_event(socket, chart_id, %{"option" => _} = params) when is_binary(chart_id) do
params = Map.put_new(params, "merge", true)
push_event(socket, "echarts:#{chart_id}:init", params)
end
def update(%{id: id, option: option, merge: merge} = assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> push_init_event(id, %{"option" => option, "merge" => merge})}
end
@impl true
def render(assigns) do
~F"""
<div phx-hook="Echarts" id={"echarts-#{@id}"} phx-update="ignore" class={@class} data-id={@id} />
"""
end
end
In a liveview that uses this, I push “loading” on mount and when params are changed, I push “loading” again and call start_async to fetch the new data async. When it lands, I push an “update” event with the new data. Echarts has a built in show/hide loading indicator function which I’m using. You could a similar thing with the <.async_result> component.