dwyl / phoenix-liveview-counter-tutorial

🤯 beginners tutorial building a real time counter in Phoenix 1.7.7 + LiveView 0.19 ⚡️ Learn the fundamentals from first principals so you can make something amazing! 🚀

Home Page:https://livecount.fly.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Feat: Split `counter.ex` into a basic `LiveView` Component

nelsonic opened this issue · comments

commented

At present the counter.ex file contains the following render function:

def render(assigns) do
~H"""
<div>
<h1 class="text-4xl font-bold text-center"> The count is: <%= @val %> </h1>
<p class="text-center">
<.button phx-click="dec">-</.button>
<.button phx-click="inc">+</.button>
</p>
<h1 class="text-4xl font-bold text-center"> Current users: <%= @present %></h1>
</div>
"""
end

Todo

  • Split this out into it's own LiveView Component

  • Also tidy this appearance:

image

Please Note: there's no urgent need to do this work ...
The reason I'm doing this split here in the counter (the simplest possible LiveView app)
is so that I can learn how LiveView Components work from first principals for: dwyl/mvp#141 (comment)

commented

New Phoenix default page:
image

commented

liveview-counter-1 7 7

commented

Two browsers, not connected:
liveview-counter-not-connected

commented

liveview-counter-4-windows

commented

Not quite there yet ...

[error] #PID<0.677.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.676.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /
** (exit) an exception was raised:
    ** (KeyError) key :val not found in: %{
  id: "counter",
  socket: #Phoenix.LiveView.Socket<
    id: "phx-F3ZKa8Lfq1vDggPh",
    endpoint: CounterWeb.Endpoint,
    view: CounterWeb.Counter,
    parent_pid: nil,
    root_pid: nil,
    router: CounterWeb.Router,
    assigns: #Phoenix.LiveView.Socket.AssignsNotInSocket<>,
    transport_pid: nil,
    ...
  >,
  __changed__: %{id: true, flash: true},
  flash: %{},
  myself: %Phoenix.LiveComponent.CID{cid: 1}
}
        (counter 1.7.7) lib/counter_web/live/counter_component.ex:8: anonymous fn/2 in CounterComponent.render/1
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:386: Phoenix.LiveView.Diff.traverse/7
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:711: Phoenix.LiveView.Diff.render_component/9
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:657: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
        (elixir 1.15.4) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
        (stdlib 5.0.2) maps.erl:416: :maps.fold_1/4
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:629: Phoenix.LiveView.Diff.render_pending_components/6
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/static.ex:252: Phoenix.LiveView.Static.to_rendered_content_tag/4
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/static.ex:135: Phoenix.LiveView.Static.render/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
        (phoenix 1.7.7) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
        (counter 1.7.7) lib/counter_web/endpoint.ex:1: CounterWeb.Endpoint.plug_builder_call/2
        (counter 1.7.7) deps/plug/lib/plug/debugger.ex:136: CounterWeb.Endpoint."call (overridable 3)"/2
        (counter 1.7.7) lib/counter_web/endpoint.ex:1: CounterWeb.Endpoint.call/2
        (phoenix 1.7.7) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
        (plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2
        (cowboy 2.10.0) /Users/n/code/phoenix-liveview-counter-tutorial/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.10.0) /Users/n/code/phoenix-liveview-counter-tutorial/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
        (cowboy 2.10.0) /Users/n/code/phoenix-liveview-counter-tutorial/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
commented

Got it working. Just had to pass the val={@val} into the LiveView Component.

commented

But have the following warnings:

warning: conflicting behaviours found. function handle_event/3 is required by Phoenix.LiveComponent and Phoenix.LiveView (in module CounterComponent)
  lib/counter_web/live/counter_component.ex:1: CounterComponent (module)

warning: conflicting behaviours found. function render/1 is required by Phoenix.LiveComponent and Phoenix.LiveView (in module CounterComponent)
  lib/counter_web/live/counter_component.ex:1: CounterComponent (module)

May I ask why you would create a "sub LiveView"? I understand that the counter is already a LiveView, the unique one, so it comes with its own state and handlers. Isn't it redundant to create a "sub LivevVew" with the exact same state? Or I might have not understood your intention!

commented

@ndrean great question. Apologies if the context is not clear from the OP.
The goal is not to complicate this project.
I'm adding an optional section in this tutorial to introduce LiveView Components.
The idea is to intro the topic in the most basic App, a counter.
Then I intend to apply this knowledge over in the MVP where is relevant to split out the Components. 👌

ok ok . Maybe the button could be a component? you could probably use <Button.display click="inc"/> in the LiveView for example with the Phoenix.Component

attr :click, :string, required: true
def display(assigns) do
 ~H""" <button phx-click={@click}><%= @click %></button> """
end
commented

Totally. There are additional opportunities for splitting out out further. PRs welcome. 👌
I'm just wrapping up the docs on this and updating the links to the Fly.io App. 🔗

commented

liveview-counter-4-windows-component

commented

With presence:

dwyl-liveview-counter-presence-genserver-state