Form errors not being shown

@carlgleisner I found the entire process quite frustrating to be honest. In order to get the behaviour I wanted I ended up just handling everything myself. I used AshPhoenix.Form to do the actual form field validation but otherwise I threw the errors and all of that myself because I couldn’t get the defaults to work the way I expected. Below is the code I used to get the behaviour I wanted. Take this with a very large grain of salt as this is my first time using phoenix forms and I could just be doing something wrong. The main things you will notice here is that I’m not using for={@form} or any field={@form[:name]} on the inputs. I’m just handling everything myself and not using any helpers because I just couldn’t get what I wanted otherwise. Below are my two relevant files, the form and the ash resource.

defmodule MyAppWeb.ContactLive do
  use MyAppWeb, :live_view

  @impl true
  def render(assigns) do
    ~H"""
    <div class="grid grid-cols-1 md:grid-cols-2 gap-4 my-8">
      <div class="text-gray-700">
        <form id="contact-form" phx-change="validate" phx-submit="submit">
          <div>
            <.input
              name="name"
              type="text"
              class={@disabled_loading_state}
              placeholder="John Doe"
              label="Name"
              value=""
              errors={@name_errors}
            />
            <.input
              name="email"
              type="text"
              class={@disabled_loading_state}
              placeholder="john@example.com"
              value=""
              label="Email"
              errors={@email_errors}
            />
          </div>
          <div>
            <.input
              name="message"
              class={@disabled_loading_state}
              placeholder="Enter your message here..."
              label="Message"
              value=""
              type="textarea"
              errors={@message_errors}
            />
          </div>
          <.button
            type="submit"
            phx-disable-with="Sending..."
            class="phx-submit-loading:cursor-not-allowed"
          >
            Submit
          </.button>
        </form>
      </div>
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    form =
      MyApp.Public.Message
      |> AshPhoenix.Form.for_create(:send_message, as: "contact")
      |> to_form()

    disabled_loading_state =
      "phx-submit-loading:cursor-not-allowed phx-submit-loading:bg-slate-50 phx-submit-loading:text-slate-500 phx-submit-loading:border-slate-200"

    {:ok,
     socket
     |> assign(:disabled_loading_state, disabled_loading_state)
     |> assign(:form, form)
     |> assign(:name_errors, [])
     |> assign(:email_errors, [])
     |> assign(:message_errors, [])}
  end

  def handle_event("validate", params, socket) do
    validate = AshPhoenix.Form.validate(socket.assigns.form, params)
    target = validate.params["_target"] |> List.first()

    case target do
      "name" ->
        {:noreply, socket |> validate_name(validate.errors[:name])}

      "email" ->
        {:noreply, socket |> validate_email(validate.errors[:email])}

      "message" ->
        {:noreply, socket |> validate_message(validate.errors[:message])}

      _ ->
        {:noreply,
         socket
         |> assign(name_errors: [])
         |> assign(email_errors: [])
         |> assign(message_errors: [])}
    end
  end

  @impl true
  def handle_event("submit", params, socket) do
    validate = AshPhoenix.Form.validate(socket.assigns.form, params)

    socket =
      socket
      |> assign(form: validate)
      |> validate_name(validate.errors[:name])
      |> validate_email(validate.errors[:email])
      |> validate_message(validate.errors[:message])

    case AshPhoenix.Form.submit(socket.assigns.form) do
      {:ok, form} ->
        {:noreply,
         socket
         |> put_flash(:info, "Thank you for your message #{form.email}!")
         |> push_navigate(to: ~p"/contact")}

      {:error, form} ->
        {:noreply, socket |> assign(form: form)}
    end
  end

  defp validate_message(socket, errors) when is_nil(errors) do
    socket |> assign(message_errors: [])
  end

  defp validate_message(socket, errors) do
    {message, _} = errors
    socket |> assign(message_errors: [message])
  end

  defp validate_email(socket, errors) when is_nil(errors) do
    socket |> assign(email_errors: [])
  end

  defp validate_email(socket, errors) do
    {message, _} = errors
    socket |> assign(email_errors: [message])
  end

  defp validate_name(socket, errors) when is_nil(errors) do
    socket |> assign(name_errors: [])
  end

  defp validate_name(socket, errors) do
    {message, _} = errors
    socket |> assign(name_errors: [message])
  end
end

Ash Resource

defmodule MyApp.Public.Message do
  @moduledoc """
  Resource for creating messages from the contact form that is publicly available
  """
  use Ash.Resource,
    domain: MyApp.Public,
    data_layer: AshPostgres.DataLayer

  postgres do
    table "public_contacts"
    repo MyApp.Repo
  end

  actions do
    defaults [:read, :destroy, update: :*]

    create :send_message do
      accept [:email, :message, :name]

      validate match(
                 :email,
                 ~r<^[a-zA-Z2-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$>
               ) do
        message "invalid email"
      end
    end
  end

  attributes do
    uuid_primary_key :id

    attribute :email, :ci_string, allow_nil?: false, public?: true
    attribute :message, :string, allow_nil?: false, public?: true
    attribute :name, :string, allow_nil?: false, public?: true
    attribute :archived_at, :utc_datetime_usec, public?: true
    attribute :deleted_at, :utc_datetime_usec, public?: true

    create_timestamp :created_at, public?: true
    update_timestamp :updated_at, public?: true
  end
end