phoenix-liveview-counter-tutorial icon indicating copy to clipboard operation
phoenix-liveview-counter-tutorial copied to clipboard

Feedback on tutorial (from a beginner's perspective)

Open j-n-f opened this issue 1 year ago • 8 comments

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.

j-n-f avatar Nov 02 '24 01:11 j-n-f

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.

j-n-f avatar Nov 02 '24 02:11 j-n-f

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.

j-n-f avatar Nov 02 '24 02:11 j-n-f

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.

j-n-f avatar Nov 02 '24 02:11 j-n-f

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 ".

j-n-f avatar Nov 02 '24 02:11 j-n-f

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.

j-n-f avatar Nov 02 '24 02:11 j-n-f

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.

j-n-f avatar Nov 02 '24 03:11 j-n-f

We could re-build this tutorial to use https://livebook.dev for more interactivity. 💭

nelsonic avatar Nov 03 '24 09:11 nelsonic

@j-n-f A quick response.

  1. In the HTML, you have phx-click = "inc"
  2. On-click, the client sends a message - via the LiveSocket - to the Liveview process. The message starts with "inc".
  3. The Liveview has message callbacks or handlers such as handle_event for 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.
  4. 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_info callback 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 the mount/3 callback, right at the beginning on the Liveview.
  • count value is part of the message in handle_event. This function returns by updating the "assigns". Any modification of an assign (:val here corresponding to @val in 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, count is also passed to the PubSub because you want to propagate this info to connected clients. The Liveview callback for non-client message is handle_info. This function again returns by updating again the socket assigns val. Thus some story as above. So it is logical to use count twice, 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.

ndrean avatar Nov 03 '24 12:11 ndrean