Issues with render_block after updating to Phoenix 1.16

I feel like I’ve been spamming the forms of late as I try to upgrade an older LiveView project to Phoenix 1.16 and LV 0.17…

Anyway I have a component that renders a block:

  @spec render(map()) :: LiveView.Rendered.t()
  def render(assigns) do
    ~H"""
        <.form let={f} for={@changeset} class="form" phx-change={"#{String.downcase(@type)}_change"} phx-submit={"#{String.downcase(@type)}_save"}>
          <%= render_block(@inner_block, f: f) %>
        </.form>
    """
  end

The idea being that you can pass in a changeset and render the form fields as individual components. Like this:

<%=
  live_component ProductInputComponent,
    title: "Colors",
    type: "Color",
    entries: @colors,
    changeset: @color_changeset do %>

  <%= render_live_flash_messages @flash, key_prefix: :color_, close_event: "clear_flash" %>
  <%=
    live_component FormGroup.TextInputComponent,
      f: @f,
      field: :value,
      label: "Color",
      input_class: "form-control--select-height",
  %>
  <%=
    live_component FormGroup.MultiplierInputComponent,
      f: @f,
      field: :price_multiplier,
      label: "Price Multiplier",
      step: 0.001,
  %>
<% end %>

In this particular instance we are using the above template as a partial inside another form:

<%=
  f =
    form_for @changeset, "#",
      id: "product_form",
      phx_change: "product_change",
      phx_submit: "product_save"
%>
...
 <%= if "color" in @child_form_display  do %>
        <%= render "_add_color.html",
          socket: @socket,
          color_changeset: @color_changeset,
          f: f %>
      <% end %>
...
<% end %>

The parent form is for @changeset and then we may also include a partial with the @color_changeset. The ProductInputComponent used to render a separate form for @color_changeset, but now it seems like the @f in the partial is referring to @changeset and not @color_changeset.

Previously the ProductInputComponent generated inputs like this:

<input class="input-border form-control--select-height" id="product_color_value" name="product_color[value]" type="text">

Now it looks like this →

<input class="input-border form-control--select-height" id="product_form_value" name="product_form[value]" type="text">

The relevant change here is that the name (and ID) refers to the top level @changeset and not @color_changeset. Which means that when submitting data from the form everything is lumped into the product_form: %{} params whereas previously the params looked like: %{product_form: %{}}, %{product_color: %{}}

It seems like render_block/2 isn’t passing the updated f value back. I know this function is deprecated but from what I can tell it should still work- I think…

What am I missing?

You’re giving it the f from the product form so it seems natural that it would become part of that form.

If you’re trying to nest forms, that’s invalid HTML. You could use inputs_for if you want it embedded or create the two forms and immediately close them (i.e. no contents) then set the appropriate form attribute on each input.

Side note: do you really need all your inputs to be live components? Not just function components?