phoenix-liveview-counter-tutorial
phoenix-liveview-counter-tutorial copied to clipboard
Feedback on tutorial (from a beginner's perspective)
Total elixir newbie here (so my ignorance is pretty high, but maybe this is a useful perspective).
Invoking CounterWeb.Endpoint.broadcast_from sends a message from the current process self() on the @topic, the key is "inc" and the value is the new_state.assigns Map.
I think for people coming from other languages/frameworks it's a bit confusing to mention that the key is "inc". I don't see that key pop up anywhere else after that, or see how the "inc" value is relevant. It looks like the new state of :val is already set and sent to the topic. Is it going to call handle_event("inc", ...) again? It doesn't look like it, it seems like handle_info(...) is going to update assigns for all the connected clients.
To put the question another way, why should I pass "inc" instead of "foo"?
Everything else is clear, but that's the one bit that doesn't make sense.
Just piggybacking here:
This is fine when the template is small like in this counter, but in a bigger App like our MPV it's a good idea to split the template into a separate file to make it easier to read and maintain.
I think MPV is a typo.
Another oddity:
def render(assigns) do
~H"""
<div class="text-center">
<h1 class="text-4xl font-bold text-center"> Counter: <%= @val %> </h1>
<.button phx-click="dec" class="w-20 bg-red-500 hover:bg-red-600">-</.button>
<.button phx-click="inc" class="w-20 bg-green-500 hover:bg-green-600">+</.button>
</div>
"""
end
Initial example shows <.button ...>
but later (when the template goes in its own file):
def render(assigns) do
~H"""
<div class="text-center">
<h1 class="text-4xl font-bold text-center"> Counter: <%= @val %> </h1>
<button phx-click="dec" class={btn("red")}>
-
</button>
<button phx-click="inc" class={btn("green")}>
+
</button>
</div>
"""
end
The . prefix disappears. No explanation is given.
I think most people will find the . prefix most surprising, so maybe a little explanation can be given for what that means after showing it the first time, or at least mention that it will be explained later.
Finally, we need make some changes to the LiveView itself, it now has less to do!
defmodule CounterWeb.Counter do
use CounterWeb, :live_view
alias Counter.Count
alias Phoenix.PubSub
@topic Count.topic
def mount(_params, _session, socket) do
if connected?(socket) do
# why are we using Counter.PubSub?
# vvvvvvvvvvvvvv this is aliased the same as Phoenix.PubSub
PubSub.subscribe(Counter.PubSub, @topic)
end
{:ok, assign(socket, val: Count.current()) }
end
# ...
end
This bit also doesn't make sense. I know that Counter.PubSub could be something completely different, but it really does look like it references the same thing.
Create a file with the path: lib/counter_web/live/counter_state.ex and add the following:
def handle_call(:current, _from, count) do
{:reply, count, count}
end
defp make_change(count, change) do
new_count = count + change
PubSub.broadcast(Counter.PubSub, topic(), {:count, new_count})
{:reply, new_count, new_count}
end
Why is the count value returned twice in these functions?
What is Counter.PubSub? The module is called Counter.Count. I don't see where Counter.PubSub is defined. I'm guessing alias Phoenix.PubSub would be fully qualified in this scope as Counter.Count.PubSub so it wouldn't be referring to that. It's not anywhere in counter.ex.
edit: found it: https://github.com/dwyl/phoenix-liveview-counter-tutorial/blob/main/lib/counter/application.ex#L16
~~I don't think the application.ex was covered.~~ application.ex is mentioned in passing, but it seems like an important element of how the framework functions. It might be nice to have it explained, what it does, how it relates to other things, where/if it has to be registered anywhere else in the application, etc. At a minimum, maybe something like "you can read about what this file is and what it does at ".
Some of these could be considered more general bits of Elixir trivia (namespacing, etc.).
I think with the earlier claim:
Basic familiarity with Elixir syntax is recommended but not essential
It's looking to me like it probably is essential. Maybe there are ways to gently fold (some of) these bits of knowledge into the tutorial.
And that's all I've got for now.
It's a massive effort to mix together this amount of prose and code and I appreciate all the work you've put into it.
One thing that impressed me lately is the format of the Svelte tutorial. I don't know if there's an interactive tool like this for Elixir, but I could see the content in this tutorial fitting that kind of format really nicely. I did a little search, but so far I don't see anything that would run the BEAM in wasm or something like that, but it would be really cool if it did.
We could re-build this tutorial to use https://livebook.dev for more interactivity. 💭
@j-n-f A quick response.
- In the HTML, you have
phx-click = "inc" - On-click, the client sends a message - via the LiveSocket - to the Liveview process. The message starts with "inc".
- The Liveview has message callbacks or handlers such as
handle_eventfor client messages. This function pattern matches the arguments on the received messages. Here, on "inc", it does something. This is the "normal" way-to-go. - In this handler, you happen to broadcast. This is a different process. You can see that it is started in the "Application" module. This PubSub needs obviously some identifier. The identifier or "topic" happens also to be "inc". Then any subscribed user on this topic will receive a message via a
handle_infocallback this time. And again you have pattern matching on the arguments of the callback. This is how messages are selected on Elixir via pattern matching on arguments. Note that a user subscribed to the PubSub topic in themount/3callback, right at the beginning on the Liveview.
-
countvalue is part of the message inhandle_event. This function returns by updating the "assigns". Any modification of an assign (:valhere corresponding to@valin the markup, syntactic sugar b.t.w) that is used in the rendered HTML will trigger an atomic update/render. This is how things work. -
as said before,
countis also passed to the PubSub because you want to propagate this info to connected clients. The Liveview callback for non-client message ishandle_info. This function again returns by updating again the socket assignsval. Thus some story as above. So it is logical to usecounttwice, of course for a different purpose.
Once you understand this rendering mechanism, and that you are just passing messages (mostly via some hidden websocket) to processes with callbacks working with pattern matching, you are good to go deeper. The philosophy is rather simple compared to other web frameworks.
Then indeed, <.button> is a Phoenix Component, whilst <button> is a simple HTMLElement. Perhaps a typo, I did not try.