Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback on tutorial (from a beginner's perspective) #227

Open
j-n-f opened this issue Nov 2, 2024 · 8 comments
Open

Feedback on tutorial (from a beginner's perspective) #227

j-n-f opened this issue Nov 2, 2024 · 8 comments

Comments

@j-n-f
Copy link

j-n-f commented Nov 2, 2024

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
Copy link
Author

j-n-f commented Nov 2, 2024

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
Copy link
Author

j-n-f commented Nov 2, 2024

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
Copy link
Author

j-n-f commented Nov 2, 2024

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 j-n-f changed the title Documentation on sending messages to topics could be clearer Feedback on tutorial (from a beginner's perspective) Nov 2, 2024
@j-n-f
Copy link
Author

j-n-f commented Nov 2, 2024

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
Copy link
Author

j-n-f commented Nov 2, 2024

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
Copy link
Author

j-n-f commented Nov 2, 2024

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.

@nelsonic
Copy link
Member

nelsonic commented Nov 3, 2024

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

@ndrean
Copy link
Contributor

ndrean commented Nov 3, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants