phx-submit and phx-click interaction and dynamically rendered sub-forms


I’m tring to create a form inside a live view that allows the user to dynamically add associated items to an Ecto schema. My schemas look a little like this:

defmodule Post do
    schema "posts" do
        field :topic, :string
        has_many :comments, Comment

defmodule Post do
    schema "posts" do
        field :content, :string
       belongs_to :post, Post

I want to createa a form that allows a user to create a Post and 1 or more Comments at the same time. This looks a little like:

<.form phx-submit="save" ...>
<!-- post form here -->
<%= inputs_for ... %><!-- comment for here --><% end %>

Underneath the comment forms is an add button, when the user clicks that a new comment is added to the form. This is handled through phx-click and on the server an event handler simply adds a new empty %Comment{} to the changeset data and rerenders the form. Similarly, all the comments have a delete button, that removes the specified comment and rerenders.

This all works fine, except for one important detail. When the user fills out the Post form and than clicks the “Add comment” button, without submitting the form, a new empty comment is added. However, since this is handled through phx-click, the form is never submitted and the changeset is never updated. So on rerendering the previously filled out form is now empty again.

I understand why this is happenign, I just don’t understand how to fix this. I have tried a couple things.

First I tried looking for a way to submit the entire form with the phx-click, in that case I would be able to update the changeset. However, according to the docs this is not possible, the px-click only ever sends the value of the clicked element as event param.

The next thing I tried was modifying the save handler to accept a situation where the add button was clicked, instead of the submit button. In this case the problem is the other way around, I can’t figure out a way to determine which button was clicked.

The third thing I tried was leaving the phx-click and phx-submit both in their place, and changing the button to type=“submit”. This actually works, the server handles the ‘add’ event first and than the savce event. However, I can’t really find any documentation on wether or not the events are processed in order. Besides, this would submit the form every time the add button is clicked, and potentially persist it in the database, which is not the intended effect.

What would be a good way to handle this case?

So you have something like :on-click="add_comment" (type="button")
And in that handle event handle_event("add_comment", _params, socket) in socket you should have your changed changeset: socket.assigns.changesetand for that changeset you need to add new comment data and {:noreply, assign(socket, changeset: changeset)}
As far as I understand that should work, could you elaborate what you’re currently doing in on click event?

What you’re suggesting is what I described as option 1. However, this doesn’t work, because the add comment button triggers an event throught phx-click, which only sends the value of the button itself to the server, as apposed to the contents of the entire form. This means I can’t update the changeset on the server without the user losing their input.

For example, let’s say the user has filled in the Post form and added a title to their post, but they don’t submit yet. They then click the “add comment” button to add a comment. Tihs click will emit an event to the server that does not include the values in the post form. The changeset is updated with a new empty %Comment{} and the form is rerendered, at that point the previously filled in values are erased because they where not yet in the changeset.

Is the button type button? Because if it’s implicit submit it will trigger submit form. But if you explicitly set button type="button" then it will only trigger phx-click event. And you don’t need the parameter that that button sends because in the socket you have your changed changeset from previous handling of change events, yes?