## Introduction

This is a post about getting a form in a LiveView to invoke a Phoenix controller action, on your terms. If you want to deploy a Phoenix LiveView app right now, then check out how to [get started](/content/docs/elixir/index.html). You can be up and running in minutes.

Have you ever wanted to use LiveViews for a site’s authentication? Among many other implementation details, you need to save some data to identify the logged-in user. This can be a token or some unique identifier, and it needs to persist even as the user navigates around your app and different LiveViews get created and destroyed.

The obvious solution is to store this token or unique identifier in the session. You can create a Phoenix controller with a `:create` action that generates a token, then saves it in the session using functions of the `Plug.Conn` module:

```elixir
defmodule MyAppWeb.SessionController do
  use MyAppWeb, :controller

def create(conn, %{"user" => user}) do
    token = Accounts.generate_user_session_token(user)

conn
    |> put_session(:user_token, token)
    |> redirect(to: signed_in_path(conn))
  end
end
```

You continue building your authentication system and decide that once a user signs up, using a form in a LiveView, they should be automatically logged in. This means saving the session data from within the LiveView—and only after the new user is finished signing up and you’re happy for them to have access to the app.

## Problem

The LiveView lifecycle starts as an HTTP request, but then a WebSocket connection is established with the server, and all communication between your LiveView and the server takes place over that connection.

Why is this important? Because session data is stored in cookies, and cookies are only exchanged during an HTTP request/response. So writing data in session can’t be done directly from a LiveView.

Can we call the controller’s `:create` action from our LiveView form, and have it write the data for us? And can we make sure that happens only once the new user’s registration process is complete: their data validated and saved?

## Solution

We can make an HTTP route call to a controller when the form is submitted by adding the `:action` attribute to our forms, specifying the URL we want to use.

And the [:phx-trigger-action](https://hexdocs.pm/phoenix_live_view/form-bindings.html#submitting-the-form-action-over-http) attribute allows us to make form submission conditional on some criteria.

In this case, we want to trigger the form submit, and log the new user in, after saving their registration data in the database without errors; if this doesn’t happen, the action should not trigger, and instead we need to keep our LiveView connected and display any generated errors.

Let’s see how to do it.

Let’s start by defining, in our LiveView, the form that we’ll use to fill out the user’s data:

```elixir
def render(assigns) do
  ~H"""
  <h1>Register</h1>

<.form
    id="registration_form"
    :let={f}
    for={@changeset}
    as={:user}
  >
    <%= label f, :email %>
    <%= email_input f, :email, required: true %>
    <%= error_tag f, :email %>

<%= label f, :password %>
    <%= password_input f, :password, required: true %>
    <%= error_tag f, :password %>

<div>
      <%= submit "Register" %>
    </div>
  </.form>
  """
end
```

This form uses a changeset to build the necessary inputs. In this case, just a couple of inputs to save the user’s email and password.

We define the changeset and add it to the LiveView assigns:

```elixir
def mount(_params, _session, socket) do
  changeset = Accounts.change_user_registration(%User{})
  {:ok, assign(socket, changeset: changeset)}
end
```

We also add a couple of callbacks: `validate` to validate the data that the user enters into the form, and show us live errors if needed, and `save` to persist the user’s information into the database.

```elixir
def handle_event("validate", %{"user" => user_params}, socket) do
  changeset = Accounts.change_user_registration(%User{}, user_params)
  {:noreply, assign(socket, changeset: changeset)}
end

def handle_event("save", %{"user" => user_params}, socket) do
  case Accounts.register_user(user_params) do
    {:ok, user} ->
      changeset = Accounts.change_user_registration(user)
      {:noreply, assign(socket, changeset: changeset)}

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

We add three more attributes to our form: `:phx-submit`, `:phx-change` and `:action`. The first two invoke the callbacks we defined above, and `:action` executes our controller’s `:create` action using the URL `users/log_in/`.

```elixir
<.form
  id="registration_form"
  :let={f}
  for={@changeset}
  as={:user}
  phx-submit="save"
  phx-change="validate"
  action={~p"/users/log_in/"} #{Routes.session_path(@socket, :create)}
>
```

With this, we get the `:create` action to run once the form is submitted; however, the action will run happily even if there was an error saving the user data. We don’t want that!

This is where the `:phx-trigger-action` attribute comes into play. Let’s use it to submit the form only if the user has been successfully saved to the database.

First we add the `phx-trigger-action` attribute to the form:

```elixir
<.form
  id="registration_form"
  :let={f}
  for={@changeset}
  as={:user}
  phx-submit="save"
  phx-change="validate"
  action={~p"/users/log_in/"} #{Routes.session_path(@socket, :create)}
  phx-trigger-action={@trigger_submit}
>
```

You can probably see where this is going: `phx-trigger-action` takes a boolean value, so when `@trigger_submit` is `true`, the form will get submitted and the action defined in our `action` attribute will be triggered. Let’s add `trigger_submit` to the LiveView assigns:

```elixir
def mount(_params, _session, socket) do
  changeset = Accounts.change_user_registration(%User{})
  {:ok, assign(socket, changeset: changeset, trigger_submit: false)}
end
```

We change `trigger_submit` to `true` only if the user has been saved correctly:

```elixir
def handle_event("save", %{"user" => user_params}, socket) do
  case Accounts.register_user(user_params) do
    {:ok, user} ->
      changeset = Accounts.change_user_registration(user)
      socket = assign(socket, changeset: changeset, trigger_submit: true)
      {:noreply, socket}

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

Now the `:create` action is only executed once the user is saved correctly. In case of error, the LiveView shows the registration errors to the user.

## Possible errors and how to fix them

Let’s prevent two common errors that can trip us up when using the `phx-trigger-action` option.

### Form parameters are empty when phx-trigger-action is triggered

The first error is very specific to our use case and is related to our form fields: When the form is submitted and the form’s parameters reach the controller, the parameter that stores the user’s password is empty, even though we’re sure we’ve entered a value.

This is related to the password type input and its [behavior](https://hexdocs.pm/phoenix_live_view/form-bindings.html#password-inputs). All we have to do is explicitly give the input a value by adding the `value` option:

```elixir
<%= password_input f, :password,
  required: true,
  value: input_value(f, :password)
%>
```

With this simple step, the value of our password input will be sent in the parameters of the form!

### The controller route is not found, even though it is defined in the router

The second mystifying error is this: `phx-trigger-action` tries to execute the controller action we specified, but the route cannot be found on the router, even when it is the correct one.

```elixir
[debug] ** (Phoenix.Router.NoRouteError)
  no route found for PUT /users/log_in (MyAppWeb.Router)
```

In this case, it’s related to how our changeset is interpreted when the form and its attributes are being built.

In our example, we insert the user into the database just before submitting the form, so our changeset contains the data of a record that already exists. Phoenix thinks that we’re trying to modify that record; that’s when the form is built using the `put` method instead of the `post` method.

The solution is simple; we just have to add the option `method="post"` to our form’s definition:

```elixir
<.form
  id="registration_form"
  :let={f}
  for={@changeset}
  as={:user}
  phx-submit="save"
  phx-change="validate"
  action={~p"/users/log_in/"} #{Routes.session_path(@socket, :create)}
  phx-trigger-action={@trigger_submit}
  method="post"
>
```

## Discussion

The `phx-trigger-action` option is ideal when you need to do final validations just before submitting form data via an HTTP request from a LiveView.

It’s also so simple to use that you’d think nothing could go wrong. However, as we’ve seen, headaches can arise from the underlying form behavior, and they can be tricky to debug. We’ve highlighted two such problems to help you use the `phx-trigger-action` option painlessly.
