I am struggling to implement a simple timer in LiveView, and so I was wondering if anyone knew of any good examples out there that I could take a look at? I just want to display a simple countdown timer on the page, and have access to those values (the count). Thanks
I’m not sure about other examples out there but here is how I would approach just a simple countdown using erlang’s :timer
defmodule MyAppWeb.ExampleLive do
use MyAppWeb, :live_view
def mount(_params, _session, socket) do
if connected?(socket), do: :timer.send_interval(1000, self(), :tick)
{:ok, assign(socket, %{count: 60})}
end
def handle_info(:tick, socket) do
{:noreply, assign(socket, count: socket.assigns.count - 1)}
end
def render(assigns) do
~H"""
<div><%= @count %></div>
"""
end
end
Calling :timer.send_interval will send a message to the liveview process (i.e. itself) that can then be handled to decrement the current count. The count variable will be available in socket.assigns.count to use elsewhere or to add some logic when the timer reaches zero etc.
I guess this is why this is driving me so crazy. I love how simple it is, right. But … it doesn’t work. I cannot for the life of me get handle_info to receive anything. I remember reading it was only for a GenServer, and then recently read that it was not, but I am starting to think maybe the original (probably one randomly stumbled-upon) item I read might have been right?
I’m just kind of at a loss for words. A few of the random thoughts I have had were: 1. Is it an issue with the self() → Are we able to send to a LiveView, in that, is it a “Process” we can send it to. I know it is a process, but is it? (I hope you know what I mean there and I say that partly in jest). 2. Is is the message format but I have tried non-atoms and all other sorts and nothing seems to give.
Here is my cleaned up version of what I am working with now. I can gladly show you all the other iterations I have tried as I have pretty much thrown the kitchen sink at this thing.
I really appreciate people taking the time to have a look and offer some assistance though … it really help
defmodule SurfaceAppWeb.Components.Timer do
use Surface.LiveComponent
data seconds, :integer
def mount(socket) do
if connected?(socket), do: :timer.send_interval(1000, self(), :tick)
{:ok, socket |> assign(seconds: 15)}
end
def render(assigns) do
~F"""
<div>
<h2>{ @seconds }</h2>
</div>
"""
end
def handle_info(:tick, socket) do
IO.puts "hey"
{:noreply, assign(socket, seconds: socket.assigns.seconds - 1)}
end
end
Thanks for the link. I cannot say I am all that familiar with DockYard Academy but it looks promising. I think I saw some similar examples and they were helpful, but I am looking for the automated counter example. Part of the reason why it is so frustrating is how difficult it is compared to these examples though where user action signifies the event. Just need to bridge that gap.
A liveview is kind of like a genserver underneath, right @chrismccord? I think I read or heard you saying that somewhere.
Anyway, make sure that your socket is connecting maybe that’s why you’re not getting it to work.
My brother on Brave Browser sometimes has this weird bug where the blue phx loading thing hangs for a long time and then it resolves itself and the socket connects normally after that. Do you see the blue loading bar at the top of the screen loading infinitely?
Your brother is definitely correct and Brave sockets behave strangely, but unfortunately I get the same issues in Chrome and Edge. I will do some more verifying on the connection but seems that all other aspects of the app work, just that handle_info call.
this minimal example works for me, ie getting these warnings of unhandled msgs and responding to GET with “test”.
debug] warning: undefined handle_info in ToolPocWeb.ToolLive. Unhandled message: :tick
defmodule TestWeb.TestLive do
use TestWeb, :live_view
def mount(_, _, socket) do
if connected?(socket), do: :timer.send_interval(1000, self(), :tick)
{:ok, socket}
end
def render(assigns) do
~H"""
test
"""
end
end
I think the academy curriculum is a good companion to exercism.io. I had the privilege of going through a beta cohort and being compelled to work through the materials 5-6 days a week helped solidify concepts I wasn’t grokking. Exercism’s syllabus is a great way of gating complex topics behind fundamentals but I think analysis paralysis made it hard for me to stick with it. The academy curriculum also covers topics outside of language fundamentals.
LiveViews are a genserver under the hood so what you have should work according to the proposed solutions. I prefer Process.send_after personally but you have to call it every time you process the message. handle_info is used to capture messages you pass from inside or outside the current process. From the Surface and LiveView docs it’s possible you may need to use Phoenix.LiveView — Phoenix LiveView v0.18.8. That isn’t supposed to be a concern for just a genserver but liveviews and their components take a little extra work to synchronize the state between processes.
This is a LiveComponent, not a LiveView, which means its rendered “embedded” is another process – not as its own process. If you stick a handle_info in the module that renders this, does a message come through?
Ah, @zachallaun I think that will do it. I am able to get the handle_info on the LiveView, so I think I just need to do some restructuring a little bit. Woof. Thank you so much, that was really driving me crazy. Of course I am sure the info was probably right there in front of me at some point - gotta work on that I suppose
Thanks again, I really appreciate the help from everyone. I am off to read more on LiveComponents
I’d recommend this section of the docs. Also, at the risk of going too far, I think you’re doing yourself a disservice by using Surface while you’re getting started with and familiar with LiveView. I think Surface is awesome, but especially with LiveView 0.18+, much of the gap has been closed, and it will be much easier to find solutions/understand what’s going on if you don’t have to look in two places.
For instance, I think the Surface automatically delegates handle_event to live components, e.g.
# When using Surface
use Surface.LiveComponent
def render(assigns) do
~F"""
<button :on-click="some_event">Button</button>
"""
end
def handle_event("some_event", _, socket) do
...
end
# When using LiveView
use Phoenix.LiveComponent
def render(assigns) do
~H"""
<button phx-click="some_event" phx-target={@myself}>Button</button>
"""
end
def handle_event("some_event", _, socket) do
...
end
Notice that in the Phoenix.LiveComponent version, you have to explicitly target @myself for the event to go to the live component and not the parent live view. I believe Surface does that delegation automatically, but it obviously can’t delegate send(self(), ...) because it doesn’t have that level of control.
I don’t think you’re going too far. It’s kind of funny I chose Surface because of React experience and some similarities I saw so I thought it might actually be EASIER to kinda bridge the gap, but shows what I know I guess.
Yeah being able to stick to one set of docs is always useful too & thanks for the link on that front. Just glad I am finally unstuck … feels good.