Form in live component not resetting on phx-submit

This must be something minor, but I can’t seem to put my finger on it. Why are the values of the <.form> not reset on a successful phx-submit?

defmodule AppWeb.SomePageLive
  def render(assigns) do
    ~H"""
      <.live_component
        id="some-id"
        module={AppWeb.PostingBarComponent}
        topic={@selected_topic}
        user={@current_user}
      />
    """
  end
end
defmodule AppWeb.PostingBarComponent
  use Phoenix.LiveComponent
  use AppWeb, :html
  import Phoenix.HTML.Form

  def mount(socket) do
    {:ok, assign(socket,
      post_draft: to_form(empty_changeset())
    )}
  end

  def render(assigns) do
    ~H"""
    <div>
      <.form
        for={@post_draft}
        id="form-id"
        phx-submit="submit_post"
        phx-target={@myself}
      >
        <%= textarea(@post_draft, :content,
          id: "post-input",
          rows: 1,
          placeholder: "Post to ##{@topic.name}"
        ) %>
        <button type="submit">Submit</button>
      </.form>
    </div>
    """
  end

  def handle_event("submit_post", %{"post" => post}, %{assigns: assigns} = socket) do
    %{topic: topic, user: user} = assigns
    %{"content" => content} = post

    case App.Social.create_post(content, topic.id, user.id) do
      {:ok, _} ->
        {:noreply, assign(socket,
          post_draft: to_form(empty_changeset())
        )}

      {:error, changeset} ->
        {:noreply, assign(socket, post_draft: to_form(changeset))}
    end
  end

  defp empty_changeset() do
    %App.Social.Post{}
    |> Ecto.Changeset.change()
  end
end

Is seems like {:noreply, assign(socket, post_draft: to_form(empty_changeset()))}, after a successful submit, is not triggering the expected component rerender.

Away from computer, so can’t check, but I guess it could be the fact that the value of th assign post_draft on mount and after submit are the same. Both are an empty changeset.

I could update the form assign on phx-change in addition to phx-submit, but that seems suboptimal, since I am only interested in the value of the form input directly after the submit event.

Or are you supposed to use phx-change? Be it, with or without debouncing?

I ran into this problem the other day. The only good solution I found was to use phx-change as you suggested. Another thing that worked was assigning a random ID – to_form(id: my_random_id) – to the form but that feels like a hack.

2 Likes

Ha, I’ve been silently coming back to this thread and couldn’t for the life of me figure out what the problem was (I didn’t actually try and reproducing it locally, of course).

I always add ids to forms for testing purposes so that explains why I’ve never run into it. For that reason I don’t find it particularly hacky—Since LiveView is all about ids, I’ve switched away from patterns like data-test-id to a having a convention around plain ol’ html ids which reduces a lot of noise.

I’ve been in thinking about that recently: using ids instead of data attributes for testing. Not committed to it yet, but indeed LiveView does rely a lot on ids already.

I opted for adding a private function that randomizes the ids, thanks.

  defp to_unique_form(changeset) do
    to_form(changeset, id: "form-#{System.unique_integer()}")
  end

  defp empty_changeset() do
    %App.Social.Post{}
    |> Ecto.Changeset.change()
  end

You definitely do not want to do this as it could have unintended consequences on change tracking. I had this idea before and was swiftly reminded that this was the case!

1 Like

Oh boy. Back to the keyboard :sweat_smile:.

Since you have a live component for which you must provide an id already, you can get a unique form id with: id=“#{@component_id}-form}”! I’m sure some people would scoff at the redundant “form” token in their id, but I personally don’t care about that stuff and this is exactly what I do.

I thought the idea of using the form id was to nudge LiveView into rerendering the form when the form data alone does not trigger a rerender, by using a different form id on each submit. Interestingly enough all my tests pass, when I use random ids. :stuck_out_tongue:

If I just set an id with to_form(id: "some_id"), my form is not reset.

Also, I actually have little insight into how large the costs are of using phx-change="some_event" on a form, in addition to phx-submit. I catch myself assuming that the costs are relevant/not negligible.

It should be OK since you’re only setting the ID on mount and the submit event. I also used a Hook to reset the field before I went back to the phx-change because the Hook felt like duct tape instead of a real solution.

I’m curious about the costs with using phx-change because I’m using it a lot on a new project :sweat_smile:

I realized I completely read past the meaning of “random” in your original reply so sorry if my replies sounded inconsistent (I figured you meant a random hardcoded ID).

It still seems a bit cavalier to use a totally random one but maybe it’s fine if it’s just the form? I’m trying to think of a scenario where the form would re-render and the ID would change. But ya… I can’t atm.

1 Like