Phoenix LiveView Form Select Fields

Firstly, thanks for the work put into LiveView!

I created a live form with LiveView and validations are working as expected, but I have a use case to set the value of a select field on the backend based on other changes (i.e. If XX value is added to an input, then change a select field value). The actual use case is when a customer is selected, the corresponding addresses fields are autopopulated in the other parts of the form.

I’ve tried setting the field to a different value using Changeset.put_change/3 and it works great for text_inputs but refuses to update select fields even though it’s reflected correctly in the changeset that is passed to the front end. In addition, I’ve tried using assign variables and passed them to the select input value, but it’s the same issue.

Below is a small example where on the handle_event(“validate”), the changeset changes are updated to gender: “Male” and username: “john_doe”. The username updates correctly, but the select input never updates on the frontend, even though if you inspect the changeset the changes are reflected properly.

Here’s the form:

<%= f = form_for @changeset, "#", [phx_change: :validate, phx_submit: :save] %>
  <%= label f, :username %>
  <%= text_input f, :username %>
  <%= error_tag f, :username %>

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

  <%= label f, :gender %>
  <%= select f, :gender, ["Male", "Female"], prompt: "Select a gender" %>
  <%= error_tag f, :gender %>

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

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

<p><%= inspect @changeset %></p>

Here’s the LiveView:

...
def mount(_session, socket) do
    {:ok,
     assign(socket, %{
       changeset: Accounts.change_user(%User{})
     })}
  end

  def render(assigns), do: Phoenix.View.render(DemoWeb.UserView, "new.html", assigns)

  def handle_event("validate", %{"user" => params}, socket) do
    changeset =
      %User{}
      |> Demo.Accounts.change_user(params)
      |> Ecto.Changeset.put_change(:gender, "Male")
      |> Ecto.Changeset.put_change(:username, "john_doe")
      |> Map.put(:action, :insert)

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

Using this code, if I start typing a phone number in the form, the username is auto set to john_doe based on the changeset, but the select input does not change to “Male” in this case.

Not sure if I’m doing something incorrectly or it’s an issue with LiveView or Morphdom.

Thanks for the help!

4 Likes

Hello, I’ve got identical problem using LiveView. I can additionally say that selects don’t change even using JavaScript. Any solution was found already?

@kaquadu At the time I ended up add a on-blur event on the select element to trigger an event to be handled by the server. I haven’t revisited this much, but you should get much better results using the new JS hooks in Phoenix LiveView.

Did you figure out a best practice solution to this?

I haven’t circled back around to this yet, but using JS hooks or even the new LiveComponents, which could potentially manage its own state would be a good place to start. For very complex forms LiveComponent should resolve some of the issues here, but you may have to revert to something like React/Vue if it’s super complex.

Hi,

Load the values for the select from an assigns list, i.e. on mount add a list:

socket = socket
|> assign(gender_list: Data.list_genders ) # Returns list of genders e.g. id and display]

and in your .leex

<%= select f, :gender_id, Enum.map(@gender_list, &{&1.display, &1.id}) %>

Then when you validate update the list to only be the gender value you want:

socket = socket
|> assign(gender_list: Data,get_gender(id))

You will also need to use a function in your view to remove the prompt if there is only one value in the list.

Hope this helps,

Andrew

2 Likes

I ran into a similar issue - trying to use put_change to change the selected value of a select box. I followed a very similar path to the OP, but I could not find a mechanism to do it other than andrewb’s suggestion of putting options in an assign on mount and removing options I didn’t want to select anymore.