'protocol Phoenix.HTML.Safe not implemented for' a form field when using <%= %> when rendering a page

Greetings Everyone!!!

I am trying to display a form inside another form depending on the value of a field in the outer form.

If an item’s type is “I”, no other form is needed, this is just an Item.
If the item’s type is “C”, I need to show a form to add several items to this “Container”.
If the item’s type is “L”, I need to show a form to link a single item.

I already set the self-reference many to many association, but I am having problems to show the inner forms.

I actually have no idea whatsoever on how to do this, but just for starting the process, I have this code when rendering the page.

# item_live/form_component.ex
      <.simple_form
        for={@form}
        id="item-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
      ...
        <.input field={@form[:itm_descrip]} type="text" label="Description" />
        <.input field={@form[:itm_type]} type="select" label="Type" 
          options={@item_types} prompt="Choose a type:" />
        # First attempt =======================
        <div>
          <p><%= {inspect(@item_types)} %></p>
        </div>
        <div :if={@form[:itm_type] == "C"}>
          <p>Container</p>
        </div>
        <div :if={@form[:itm_type] == "L"}>
          <p>Link</p>
        </div>
        # ========================================
        <.input field={@form[:itm_cost]} type="number" label="Cost" step="any" />

Using the examples at Components and HEEx — Phoenix v1.7.18, I tried using both formats from the mentioned page, and also with the case statement.

<%= if @some_condition do %>
  <div>...</div>
<% end %>
<div :if={@some_condition}>...</div>

I also checked the format to display a variable is right…

<%= if some_condition? do %>
  <p>Some condition is true for user: {@username}</p>  # <------------
<% else %>
  <p>Some condition is false for user: {@username}</p>
<% end %>

… but I always got (I cut out a lot of fields not related and tried to split the line in a logic way):

[error] ** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for 
{"
	%Phoenix.HTML.FormField{
------>	id: \"item_itm_type\",
------>	name: \"item[itm_type]\",
		errors: [],
		field: :itm_type,
		form: %Phoenix.HTML.Form{
			source: #Ecto.Changeset<
				action: nil,
				changes: %{},
				errors: [
					...
----------->		itm_type: {\"can't be blank\", [validation: :required]},
					itm_cost: {\"can't be blank\", [validation: :required]},
					...
				],
				data: #Menu.Catalogs.Item<>,
				valid?: false,
			...>,
			impl: Phoenix.HTML.FormData.Ecto.Changeset,
			id: \"item\",
			name: \"item\",
			data: %Menu.Catalogs.Item{
				__meta__: #Ecto.Schema.Metadata<:built, :general, \"items\">,
				id: nil,
				...
				itm_cost: nil,
				...
----------->	itm_type: nil,
				...
				categories: #Ecto.Association.NotLoaded<association :categories is not loaded>,
				items_relations: #Ecto.Association.NotLoaded<association :items_relations is not loaded>,
				reverse_relations: #Ecto.Association.NotLoaded<association :reverse_relations is not loaded>,
				action: nil,
				hidden: [],
				params: %{},
				errors: [],
				options: [method: \"post\"],
				index: nil
			},
			value: nil
		}
	"} of type Tuple. This protocol is implemented for the following type(s): Atom,
	BitString,
	...
	URI

And that’s all I have tried for this. I hope you could help me out to sort this issue.

Thanks in advance and best regards,

Greg.

Can you post the entire form_component.ex file and the stack trace of the error? The issue seems to be in one of your assigns, as you are trying to render a tuple in HTML. You probably made a mistake when assigning something into assigns.

Whenever you get Phoenix.HTML.Safe not implemented for it means you are trying to cast some Elixir value—usually a map or tuple—that can’t be cast into a string that represents valid HTML.

For example, the following will give you the same error:

<%= %{} %>

Your questions mentions “nested forms” which I assumes means a parent record that has associated child records in which case you want to look at inputs_for.

Thank you everybody for your answers and I apologize for the delay. Turned out that 5 minutes ago I realized I made three mistakes.

One of them was that I referenced the field, not the value in my if statements.

        <div :if={@form[:itm_type] == "C"}>
          <p>Container</p>
        </div>

Should have been:

        <div :if={@form[:itm_type].value == "C"}>    # <---- The ".value" was missing
          <p>Container</p>
        </div>

That was one of the reasons why my code was not working.

Even though I had chosen the Container option, the “Container” division was not showing up.

After a while I realized that even though I set the values as strings…

    item_types = [
      {"Item", "I"},
      {"Container", "C"},
      {"Link", "L"}
    ]

… to set the Item’s Type…

        <.input field={@form[:itm_type]} type="select" label="Type" 
          options={@item_types} prompt="Choose a type" />

… the field’s value was an atom. When I saw that I remembered I set an Enum type in the schema…

    field :itm_type, Ecto.Enum, values: [:I, :C, :L]

That was my second mistake. After comparing against an atom, everything worked fine.

        <div :if={@form[:itm_type].value == :C}>
          <p>Contenedor</p>
        </div>

The last one was that I was using <%= %> and {...} together. As you might see in my original message I had this line of code.

          <p><%= {inspect(@item_types)} %></p>

If I had used either one of them, everything would have been just fine.

@sodapopcan, I read your answer about 1 or 2 days after you replied, but I did not get what you meant. I really failed to see how your answer was related to my problem. At that point, one month ago, I was just trying to show a single field, not a tuple. Now, I just realized I was turning my field into a tuple.

It seems like I must pay a lot more of attention to the documentation.

Best regards,

Greg