Submit triggers mount rather than event handler?

So I have a form that looks like this:

<div>
  <.form
    let={f}
    for={@changeset}
    phx-change="validate"
    phx-submit="save"
    id="edit-form">
    <div>
      <%= label(f, :username) %>
      <%= text_input(f, :username) %>
      <%= error_tag(f, :username) %>
    </div>
    <div>
      <%= label(f, :password) %>
      <%= text_input(f, :password) %>
      <%= error_tag(f, :password) %>
    </div>
    <div>
      <%= label(f, :email_address) %>
      <%= text_input(f, :email_address) %>
      <%= error_tag(f, :email_address) %>
    </div>
    <%= submit "Submit Changes" %>
  </.form>
</div>

And the corresponding event handler in my controller is this:

  def handle_event("save", %{"accounts" => accounts}, socket) do
    {:ok, socket}
  end

(it’s not returning anything for now, just the socket)

And yet, when I submit it, I get this error:

no function clause matching in NetAdminWeb.AccountsLive.Edit.mount/3

What gives? I have phx-submit="save" set up, so it should trigger handle_event/3, right? But for some reason, it’s triggering mount/2. Any idea what this could be?

You need to return {:noreply, socket} from handle_event/3:

  def handle_event("save", %{"accounts" => accounts}, socket) do
    {:noreply, socket}
  end

Check your console– odds are your callback is being invoked, then the LiveView crashes, then mount is invoked again when the client-side socket automatically reconnects.

1 Like

Thanks for your reply. I checked my console and put an IO.inspect(accounts, label: "the accounts are here") in before the {:noreply, socket}. However, the page still crashes, mount is still triggered, and there’s no output from IO.inspect. Here’s the stacktrace, if it helps at all:

lib/netadmin_web/live/accounts_live/edit.ex:7NetAdminWeb.AccountsLive.Edit.mount/3
 phoenix_live_view lib/phoenix_live_view/utils.ex:301anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
 telemetry /Users/caleb/projects/elixir/netadmin/deps/telemetry/src/telemetry.erl:320:telemetry.span/3
 phoenix_live_view lib/phoenix_live_view/static.ex:270Phoenix.LiveView.Static.call_mount_and_handle_params!/5
 phoenix_live_view lib/phoenix_live_view/static.ex:110Phoenix.LiveView.Static.render/3
 phoenix_live_view lib/phoenix_live_view/controller.ex:39Phoenix.LiveView.Controller.live_render/3
 phoenix lib/phoenix/router.ex:354Phoenix.Router.__call__/2
 lib/netadmin_web/endpoint.ex:1NetAdminWeb.Endpoint.plug_builder_call/2
 lib/plug/debugger.ex:136NetAdminWeb.Endpoint."call (overridable 3)"/2
 lib/netadmin_web/endpoint.ex:1NetAdminWeb.Endpoint.call/2
 phoenix lib/phoenix/endpoint/cowboy2_handler.ex:54Phoenix.Endpoint.Cowboy2Handler.init/4
 cowboy /Users/caleb/projects/elixir/netadmin/deps/cowboy/src/cowboy_handler.erl:37:cowboy_handler.execute/2
 cowboy /Users/caleb/projects/elixir/netadmin/deps/cowboy/src/cowboy_stream_h.erl:306:cowboy_stream_h.execute/3
 cowboy /Users/caleb/projects/elixir/netadmin/deps/cowboy/src/cowboy_stream_h.erl:295:cowboy_stream_h.request_process/3

Maybe I wrote the form wrong, or something is off in endpoint.ex? I dunno. Pretty lost. :confused:

Are you sure form data is keyed to "accounts"?

1 Like

I’m certain that it is.

NetAdminWeb.AccountsLive.Edit.mount(%{"accounts" => %{"email_address" => "email1@example.com", "password" => "pass", "username" => "bob"}}

This shows up under “params” on the Phoenix error page, so I know that’s what it is.

Also, strangely enough, I have a phx-change="validate", but the validate event handler is never triggered either.

That is the parameters on mount though, which seems to suggest your parameters are sent as URL/GET parameters. That shouldn’t really be happening for the code you showed.

You’re right. And the arguments are in the wrong order.

Okay, for context, I am migrating this thing from a pure Phoenix app to Liveview. This app was generated using mix phx.new within the past month, so it came with LiveView already installed. Is there any reason it might still be behaving like a Phoenix app rather than a LiveView one? Something in the router, maybe?

Are you sure the LV js is working as intended?

No, I am not sure of that. Is there a way to check? In app.js with console.log() or some such?

I checked in the BEAM using the Periscope tool - no Liveview processes are running!?

On that note, however, it appears that mount/3 IS being called, which I can see via IO.inspect. So apparently the liveview is being mounted, and then crashing right away.

Can you post your LV module?

It shouldn’t matter what’s in the liveview module - as @LostKobrakai pointed out, something fishy is going on because the submit button is sending params when it shouldn’t. It’s as if it’s trying to mount the liveview again because something is making it crash.

I have a hunch it’s something in endpoint.ex

You mentioned your LV is mounting and then crashing, so something inside it may be making it crash. It’s the first place we could look further to try to help you out, and then move to other parts.

1 Like

Ok! Here’s the module:

defmodule NetAdminWeb.AccountsLive.Edit do
  use NetAdminWeb, :live_view

  alias NetAdmin.Accounting
  alias NetAdmin.Accounting.Account

  def mount(%{"id" => id}, _session, socket) do
    account = Accounting.get_account_!(id)

    changeset = Account.changeset(account)

    socket =
      socket
      |> assign(account: account)
      |> assign(method: "account")
      |> assign(changeset: changeset)

    {:ok, socket}
  end

  def handle_event("validate", %{"account" => account}, socket) do
    IO.inspect(account, label: "second arg to handle_event/3")

    {:noreply, socket}
  end

  def handle_event("save", %{"account_" => account}, socket) do
    IO.inspect(account, label: "second arg to handle_event/3")

    {:noreply, socket}
  end

  defp account_emails() do
    Accounting.list_accounts()
    |> Enum.map(& &1.email_address)
  end

  defp account_prompt(nil), do: nil
  defp account_prompt(%Account{email_address: email_address}), do: email_address
end

Ok that is looking okay.

  1. What is the first error you see when you try to access the page? (if Accounting.get_account_!(id) fails, your LV will crash)
  2. Any error on the phx console?
  3. Any JS or other error on the browser console?
  4. This LV has a route defined in your router, right? Or is it inside another LV or controller?

Your “validate” handle_event has the “account” key, your “save” event has an underscore and is “account_” instead. Those defp aren´t used, did you post your entire template previously?

You can try changing your “save” and “validate” events a bit to see what’s getting in there:

  def handle_event("validate", params, socket) do
    IO.inspect(params, label: "second arg to handle_event/3")

    {:noreply, socket}
  end
1 Like

Nothing is going into the “validate” event. The error in the phx console is:

[error] #PID<0.766.0> running NetAdminWeb.Endpoint (connection #PID<0.730.0>, stream id 7) terminated                               
Server: localhost:4000 (http)
Request: GET /accounts/edit?accounts%5Busername%5D=bob&%5Bpassword%5D=spass&account_accounts%5Bemail_address%5D=email1%40example.com
** (exit) an exception was raised:
    ** (FunctionClauseError) no function clause matching in NetAdminWeb.AccountsLive.Edit.mount/3

Those other two events aren’t even being called :confused:. I’m wondering if this could be related to js or npm issues…

That’s on the questions I asked, 3 is related to JS and 4 is how you’re reaching the LV. Can you post your router code to it?

1 Like

I do have errors in my JS console. They look like this:

The relevant router code:

  pipeline :browser do
    plug(:accepts, ["html"])
    plug(:fetch_session)
    plug(:fetch_live_flash)
    # https://elixirforum.com/t/phoenix-1-6-x-what-is-put-root-layout-2-good-for/46085
    plug(:put_root_layout, {NetAdminWeb.LayoutView, :root})
    plug(:protect_from_forgery)
    plug(:put_secure_browser_headers)
    plug(:fetch_current_user)
  end

...

  scope "/", NetAdminWeb do
    pipe_through([:browser, :require_authenticated_user])

    live "/accounts", AccountsLive.Index
    live "/accounts/edit", AccountsLive.Edit

Ok so you could try inspecting what is causing the first error related to AdminLTE which could be preventing whatever is after it to work.

The live reload is also trying to connect to port 400 but that may be the port you configured instead of the default 4000?

1 Like

Thank you! This solved the issue, at least partially. I had the following in my app.js:

// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import topbar from "../vendor/topbar"
import jquery from "jquery"
import "../adminlte/js/theme"
import "../adminlte/vendor/bootstrap-notify.min.js"
import "../adminlte/vendor/jquery.min.js"

I removed these lines:

import "../adminlte/js/theme"
import "../adminlte/vendor/bootstrap-notify.min.js"
import "../adminlte/vendor/jquery.min.js"

And now the LiveView process mounts successfully, which I was able to confirm using Periscope. Thank you so much!

Now I just have to figure out how to get adminLTE to load after JQuery, because that’s the issue. Probably gonna make another thread. :joy: Thanks again!