How do I trigger the "save" handle_event in my form_component?

Hey guys, i’m working with liveview (generated by mix phx.gen.live ...) and I’m not able to save an User.

When I try to press the save button, the following error is displayed:

no route found for POST /users/new (LivekbnWeb.Router)

Here’s my index:

defmodule LivekbnWeb.UserLive.Index do
  use LivekbnWeb, :live_view

  alias Livekbn.Users
  alias Livekbn.Users.User

  @impl true
  def mount(_params, _session, socket) do
    {:ok, assign(socket, :users, list_users())}
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :edit, %{"id" => id}) do
    socket
    |> assign(:page_title, "Edit User")
    |> assign(:user, Users.get_user!(id))
  end

  defp apply_action(socket, :new, _params) do
    socket
    |> assign(:page_title, "New User")
    |> assign(:user, %User{})
  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "Listing Users")
    |> assign(:user, nil)
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    user = Users.get_user!(id)
    {:ok, _} = Users.delete_user(user)

    {:noreply, assign(socket, :users, list_users())}
  end

  defp list_users do
    Users.list_users()
  end
end

My form_component liveview

defmodule LivekbnWeb.UserLive.FormComponent do
  use LivekbnWeb, :live_component

  alias Livekbn.Users

  @impl true
  def update(%{user: user} = assigns, socket) do
    changeset = Users.change_user(user)

    {:ok,
     socket
     |> assign(assigns)
     |> assign(:changeset, changeset)}
  end

  @impl true
  def handle_event("validate", %{"user" => user_params}, socket) do
    changeset =
      socket.assigns.user
      |> Users.change_user(user_params)
      |> Map.put(:action, :validate)

    {:noreply, assign(socket, :changeset, changeset)}
  end

  def handle_event("save", %{"user" => user_params}, socket) do
    save_user(socket, socket.assigns.action, user_params)
  end

  defp save_user(socket, :edit, user_params) do
    case Users.update_user(socket.assigns.user, user_params) do
      {:ok, _user} ->
        {:noreply,
         socket
         |> put_flash(:info, "User updated successfully")
         |> push_redirect(to: socket.assigns.return_to)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, :changeset, changeset)}
    end
  end

  defp save_user(socket, :new, user_params) do
    case Users.create_user(user_params) do
      {:ok, _user} ->
        {:noreply,
         socket
         |> put_flash(:info, "User created successfully")
         |> push_redirect(to: socket.assigns.return_to)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, changeset: changeset)}
    end
  end
end

My form_component.leex

<h2><%= @title %></h2>

<%= f = form_for @changeset, "#",
  id: "user-form",
  phx_target: @myself,
  phx_change: "validate",
  phx_submit: "save" %>

  <%= label f, :name %>
  <%= text_input f, :name %>
  <%= error_tag f, :name %>

  <%= label f, :email %>
  <%= text_input f, :email %>
  <%= error_tag f, :email %>

  <%= label f, :password_hash %>
  <%= text_input f, :password_hash %>
  <%= error_tag f, :password_hash %>

  <%= submit "Save", phx_disable_with: "Saving..." %>
</form>

router

defmodule LivekbnWeb.Router do
  use LivekbnWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, {LivekbnWeb.LayoutView, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", LivekbnWeb do
    pipe_through :browser

    live "/", PageLive, :index
    live "/users", UserLive.Index, :index
    live "/users/new", UserLive.Index, :new
    live "/users/:id/edit", UserLive.Index, :edit

    live "/users/:id", UserLive.Show, :show
    live "/users/:id/show/edit", UserLive.Show, :edit

    live "/tasks", TaskLive.Index, :index
    live "/tasks/new", TaskLive.Index, :new
    live "/tasks/:id/edit", TaskLive.Index, :edit

    live "/tasks/:id", TaskLive.Show, :show
    live "/tasks/:id/show/edit", TaskLive.Show, :edit


    #post "/tasks/new", TasksController, :new
  end

  # Other scopes may use custom stacks.
  # scope "/api", LivekbnWeb do
  #   pipe_through :api
  # end

  # Enables LiveDashboard only for development
  #
  # If you want to use the LiveDashboard in production, you should put
  # it behind authentication and allow only admins to access it.
  # If your application does not have an admins-only section yet,
  # you can use Plug.BasicAuth to set up some basic authentication
  # as long as you are also using SSL (which you should anyway).
  if Mix.env() in [:dev, :test] do
    import Phoenix.LiveDashboard.Router

    scope "/" do
      pipe_through :browser
      live_dashboard "/dashboard", metrics: LivekbnWeb.Telemetry
    end
  end
end

How do I make it work?

Thank you for your patience :sweat_smile:

1 Like

Hello, you are doing a redirect to a route that is signed on the socket on this line:

 |> push_redirect(to: socket.assigns.return_to)}

Have you checked that this recorded value matches your route?

1 Like

The browser is doing a normal form POST submission to /users/new, instead of LiveView intercepting the submission click . Your router is looking for a standard Phoenix controller path defined with something like post "/users/new, UsersController, :new but obviously you want to keep this within LV with your live "/users/new", UserLive.Index, :new route.

Please post your UserLive.Index markup where you are rendering the UserLive.FormComponent, that’s where I assume the problem lies.

1 Like

Thank you guys, It was a problem with my Dockerfile.

2 Likes

Can you share what was the actual problem and solution? It may help someone else having the same issue.

2 Likes