Updating a LiveComponent's form

I have a form in a LiveComponent that doubles as a Create/New form as well as Edit. On mount/1 it assumes a new context:

def render(assigns) do
  ~H"""
    <form for={@form} ...>
      <.input field={@form[:title]}...>
  """
end

def mount(socket) do
  changeset = Challenge.changeset(%Challenge{}, %{})
  {:ok, assign(socket, :form, to_form(changeset))}
end

The user can make a selection on the page which triggers the form to load a model. Now we’re in an Edit context.

def handle_event("edit", %{"id" => id}, socket) do
    challenge = App.Repo.get(Challenge, id)
    changeset = Challenge.changeset(challenge, %{})
    {:noreply, assign(socket, :form, to_form(changeset))}
end

I would expect the form’s input field to update, but it remains blank (the default from the NEW context in mount/1).

As always, thank you!

Hi @boiserunner can you show more of your code? You aren’t showing how the "edit" event itself is triggered. Are you sure that the function is being called?

Hmm, could you show how the LiveComponent is included in its parent?

To simplify matters, for the moment, I’ve taken LiveComponent out of the equation and the fundamental issue persists. Here’s a more complete example:

def render(assigns)
  ~H"""
    <.form for={@form}>
      <.input field={@form[:title]} />
    </.form>
    <button phx-click="test">Update the form!</button>
  """
end

def mount(_params, _session, socket) do
  changeset = Challenge.changeset(Challenge, %{})
  {:ok, assign(socket, :form, to_form(changeset))}
end

def handle_event("test", _params, socket) do
  challenge = App.Repo.get!(Challenge, 1)
  changeset = Challenge.changeset(challenge, %{})
  {:noreply, assign(socket, :form, to_form(changeset))}
end

My first hunch was that, well, there are no changes in the changeset and so the LiveView doesn’t bother re-rendering. I’m stumped!

Try:

def render(assigns)
  ~H"""
    <.form for={@form} phx-submit="test">
      <.input field={@form[:title]} />
      <button type="submit">Update the form!</button>
    </.form>
  """
end

Edit:
Oh, you’re also not using the params when building the changeset in the handle_event callback:

def handle_event("test", params, socket) do
  challenge = App.Repo.get!(Challenge, 1)
  changeset = Challenge.changeset(challenge, params) # likely needs to be: params["something"] but I can't tell what the "something" is from your code
  {:noreply, assign(socket, :form, to_form(changeset))}
end

Yes and no. The form itself isn’t triggering the event to update the form (which may very well be the problem). Instead, the user clicks a button to load a record, and now my form is in an EDIT context. So I have no form values coming in when the initial event is triggered.

I’m not sure yet why a re-render is not sufficient. I’ll take a closer look at to_form and see if the re-render relies on changed values rather than the underlying data.

Are you sure the form input is not focused, which prevents phoenix from updating the form? You can test this by at the same time as updating the underlying data updating an id on the form.

That’s the winner! Thank you. Bumping the id of the form does indeed force the update I need. Why does this happen?

Because you usually don’t want to switch out the context underneight users feets while they’re editing the input. Especially not when a roundtrip to the server might be involved.

1 Like

Haven’t tested this yet, but a potential alternative to bumping the id would be to explicitly set the focus on click. LiveView also includes nifty JS Commands that can help manipulate focus.

For example: <button phx-click={JS.focus() |> JS.push("test")}>Update the form!</button>

There’s also a JS.push_focus() and corresponding JS.pop_focus() command for saving and restoring focus. If there are various buttons that update the form differently, this could be potentially very useful to explicitly set different focuses depending on the update applied.