Can't make phx-click work - (FunctionClauseError) no function clause matching

Hi, I am starting to learn LiveView and trying to make phx-click work. Here’s what I want to do: when user clicks on a table row, redirect to a page that shows details of that row.

I have added a phx-click to my row in index.html.heex:

<tr id={"customer-#{customer.id}"} phx-click="show">

I have added a dummy handler in index.ex:

  @impl true
  def handle_event("show", %{"id" => id}, socket) do
    IO.puts("Customer clicked")
    {:noreply, socket}
  end
  1. When I click the handler, I get this error in the console. Looks like LiveView is not finding my handler:
[error] GenServer #PID<0.2738.0> terminating
** (FunctionClauseError) no function clause matching in DemotwWeb.CustomerLive.Index.handle_event/3
    (demotw 0.1.0) lib/demotw_web/live/customer_live/index.ex:38: DemotwWeb.CustomerLive.Index.handle_event("show", %{}, #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, customer: nil, customers: [], flash: %{}, live_action: :index, page_title: "Listing Customers"}, endpoint: DemotwWeb.Endpoint, id: "phx-FuGGIKtRbaQdjB7h", parent_pid: nil, root_pid: #PID<0.2738.0>, router: DemotwWeb.Router, transport_pid: #PID<0.2730.0>, view: DemotwWeb.CustomerLive.Index, ...>)
  1. When I get past the above issue, how do I redirect to the show page?

Any help would be greatly appreciated.

1 Like

The second parameter of handle_event doesn’t include the id attribute. You need to use phx-value-… attributes if you want to pass data to the handler.

2 Likes

1 is answered,

2: {:noreply, push_redirect(socket, to: "the_route_you_want")}

push_redirect

1 Like

Thank you, @LostKobrakai & @gpopides for your help. I have tried your suggestions, but still can’t make this to work.

Changed the markup as follows, passing in value using phx-value-id. Here the full code in my repo.

<tr id={"customer-#{customer.id}"} phx-click="show" phx-value-id: customer.id>

Changed the event handler as follows. Here the full code in my repo.

def handle_event("show", %{"id" => id}, socket) do
  customer = Customers.get_customer!(id)
  {:noreply, push_redirect(socket, to: Routes.customer_show_path(@socket, :show, customer))}
end

From all I have read so far, the second parameter (%{"id" => id}) is coded correctly to pull in the id that is passed from the markup using phx-value-id: customer.id. But this is not working - Phoenix cannot find the matching handler:

[error] GenServer #PID<0.1031.0> terminating
** (FunctionClauseError) no function clause matching in DemotwWeb.CustomerLive.Index.handle_event/3
    (demotw 0.1.0) lib/demotw_web/live/customer_live/index.ex:38: DemotwWeb.CustomerLive.Index.handle_event("show", %{"id:" => ""}, #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, customer: nil, customers: [], flash: %{}, live_action: :index, page_title: "Listing Customers"}, endpoint: DemotwWeb.Endpoint, id: "phx-FuGMfpN0icG46QUE", parent_pid: nil, root_pid: #PID<0.1031.0>, router: DemotwWeb.Router, transport_pid: #PID<0.1023.0>, view: DemotwWeb.CustomerLive.Index, ...>)

However if I change the code to just have value as the second parameter, Phoenix is able to find the handler, but now I don’t know how to get the id from this value.

  @impl true
  def handle_event("show", value, socket) do
    IO.puts("handle_event")
    {:noreply, socket}
  end

Which way should I go now?

One more thing, can you please recommend a good step-by-step tutorial for LiveView? Everything I have found so far is dated.

I think there is a typo here, maybe change this to

<tr id={"customer-#{customer.id}"} phx-click="show" phx-value-id={customer.id}>
1 Like

LiveView is currently moving very fast so it makes sense many guides/resources are outdated. I would think Programming Phoenix LiveView: Interactive Elixir Web Programming Without Writing Any JavaScript by Bruce A. Tate and Sophie DeBenedetto is your best bet as it is being updated actively to reflect the latest changes until LiveView hits a V1.

On the other hand Phoenix LiveView Free Course | The Pragmatic Studio is still a very good learning resource. It is not up to date but it still teaches solid concepts, and there is a free version.

1 Like

Thanks, @naytro. That syntax error has gotten me almost there. Phoenix is now able to locate the handler. Handler is able to get the customer id. The last bit that is not working is that the path is not being constructed properly. I am getting this error:

** (UndefinedFunctionError) function nil.path/1 is undefined
    nil.path("/customers/3")

Here’s my handler:

  def handle_event("show", %{"id" => id}, socket) do
    customer = Customers.get_customer!(id)
    {:noreply, push_redirect(socket, to: Routes.customer_show_path(@socket, :show, customer))}
  end

It specifies the route as Routes.customer_show_path(@socket, :show, customer). I picked up this syntax from the “Show” button that exists in the .heex file and there it works perfectly fine. Can’t figure out why I am getting the nil when I call it from the .ex file. Also I don’t understand how the customer_show_path route is constructed. The router only has an entry like this:

live "/customers/:id", CustomerLive.Show, :show

Feels like there is some magic going on in here :slight_smile:

Can you run mix phx.routes and see if you find your route?

socket should be without the @:

{:noreply, push_redirect(socket, to: Routes.customer_show_path(socket, :show, customer))

Bingo. It works!!! Thanks you so much @tomkonidas.

1 Like

BTW @tomkonidas, what is the reason for needing to use the @ in .heex but not in .ex? I imagine it will trip up lot of newbies like me. In general, would be nice to see the exact same Elixir syntax in .heex and .ex.

In the HEEx template you have access to the socket via @socket, however in your handle_event/3 you don’t have all the assigns, but you do have the socket as third argument. So you are just using that variable.

Thanks, @tomkonidas that clarifies.