Custom components behave differently from core components

Hello,

In my project I need custom core components, i.e. input fields etc that are styled a bit differently. I know I can provide class for the core input component, but then it only affects the text field itself not its label.

What I tried to do is to create my own Inputs.default_input, that would behave the same way as core input, but with custom tailwind classes. I literally copied the code form the core components and adjust the looks. Everything works, except for one thing:

Let’s take email as an example. When I start typing the email, the error is displayed that it has to have @ sign. This works for both my and core components.
However, when I add the @ sign and email becomes valid, the error should disappear. Works for core component, but not for my component.

My component is added like this:

<Inputs.default_input
      field={@form[:email]}
      type="email"
      label="Email"
      placeholder="Enter email address"
 />

The implementation mirrors the core component:

... attributes ...
def default_input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
    errors = if Phoenix.Component.used_input?(field), do: field.errors, else: []

    assigns
    |> assign(field: nil, id: assigns.id || field.id)
    |> assign(:errors, Enum.map(errors, &translate_error(&1)))
    |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
    |> assign_new(:value, fn -> field.value end)
    |> default_input()
end

... non text default_inputs()...

def default_input(assigns) do
    ~H"""
    <div class="fieldset mb-2">
      <label>
        <span :if={@label} class="text-base text-base-content font-normal">{@label}</span>
        <input
          type={@type}
          name={@name}
          id={@id}
          value={Phoenix.HTML.Form.normalize_value(@type, @value)}
          class={[
            @class || "mt-1 w-full input text-base text-base-content",
            @errors != [] && (@error_class || "input-error text-base")
          ]}
          {@rest}
        />
      </label>
      <.error :for={msg <- @errors}>{msg}</.error>
    </div>
    """
  end

The only difference is tailwind classes in the default_input().
If I keep the code literally as-is, it still doesn’t work.

def default_input(assigns) do

    ~H"""

    <div class="fieldset mb-2">
        <label>
           <span :if={@label} class="label mb-1">{@label}</span>
           <input
             type={@type}
             name={@name}
             id={@id}
             value={Phoenix.HTML.Form.normalize_value(@type, @value)}
             class={[
               @class || "w-full input",
               @errors != [] && (@error_class || "input-error")
             ]}
            {@rest}
          />
       </label>
       <.error :for={msg <- @errors}>{msg}</.error>
   </div>
"""
end

But it starts to work when I do this:

def default_input(assigns) do
 ~H"""
     <.input {assigns} />
 """
end

Is there anything special about the core components that I am missing?

Keys should always refer to the comprehension item and are generally only useful if you render multiple items. It should probably still work since you most likely have a single error, but what happens if you drop the :key?

Apart from that I don’t see anything stand out, so if that doesn’t solve it please try to provide a script or repo to reproduce and open up an issue in LiveView!

Ah, this is leftover from my attempts to fix it. Doesn’t work with or without :key
Edited the original post to remove it.

While I was trying to reproduce this issue in the new repo, I found the problem.
My Inputs module was defined like this:

defmodule MyApp.Components.Inputs do
  use Phoenix.Component
  use MyApp, :html
 
...
end

:html is there to get access to translate_error(). However, I import all my custom components (indirectly) in html_helpers(). Apparently, it made a conflict even though compiler did not complain (because I import Components module, that, in its turn, has a __using__ macro that makes available other components).

I changed into:

defmodule NutrihiveWeb.Components.Inputs do
  use Phoenix.Component
  import MyApp.CoreComponents
...
end

and now the problem seems to have gone.

1 Like