inertiajs / inertia-phoenix

The Phoenix adapter for Inertia.js.
https://hexdocs.pm/inertia
Other
185 stars 8 forks source link

Form error handling #18

Closed dsignr closed 3 months ago

dsignr commented 3 months ago

Hi again, so far the library looks great! Thanks for your hard work. Maybe this is more of information than being an issue itself, but when working with forms, I noticed that the form object isn't being populated with the errors from the backend.

My product_controller.ex

def create(conn, %{"product" => product_params}) do
    case Storefront.create_product(product_params) do
      {:ok, _product} ->
        conn
        |> put_flash(:info, "Product created successfully.")
        |> redirect(to: ~p"/storefront/products")

      {:error, %Ecto.Changeset{} = changeset} ->
        IO.inspect changeset
        conn
        |> assign_errors(changeset)
        |> redirect(to: ~p"/storefront/products/new")
    end
end

My frontend looks like this.

Inside custom_form.svelte:

<form action={action} method={method} on:submit|preventDefault={handleSubmit}>
     <h1 class="text-2xl">
         Create Product
     </h1>
     <p class="mt-1 text-sm text-gray-500">
         Input details below to create product
     </p>
    <div class="mt-1">
         <Input errors={errors.name} name="product[name]" value="" />
    </div>
    ...
</form>
<script>
    import { onMount } from 'svelte';
    import { useForm } from '@inertiajs/svelte'
    import { page } from '@inertiajs/svelte'

    export let method = 'post'
    export let action = '/storefront/products'
    export let errors = {}

    let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")

    let form = useForm({
        _csrf_token: csrfToken,
        product: {
            name: null,
            price: null,
            description: null,
            features: null,
        }
    })

    function handleSubmit() {
        $form.post(action)
    }

    onMount(() => {
        errors = $page.props.errors
        errors = errors
        console.log(errors) // returns {}
        console.log($form.errors) // returns {}
        console.log(form.errors) // returns undefined
    });
</script>

Inside my input.svelte :

<div phx-feedback-for={name}>
    <input name={name} value={value} type={type} class={normalClasses}/>
    {#if errors}
        <p class="mt-1 gap-3 text-sm leading-6 text-rose-600">
            <span class="hero-exclamation-circle-mini mt-0.5 w-5 h-5 flex-none"></span>
            {errors}
        </p>
    {/if}
</div>

<script>
    import { onMount } from "svelte";

    export let name
    export let errors
    export let value
    export let type = "text"

    let normalClasses = "shadow-sm focus:ring-zinc-300 focus:border-zinc-400"
</script>

And finally, inside the new template at /storefront/products/new:

<CustomForm/>

This setup works perfectly fine. My only query is, the $form object is not populated with the errors, but rather it is being set at $page.props.errors. My controller has defined |> assign_errors(changeset) too as shown above. I checked the documentation at https://inertiajs.com/forms and it seems like from their example, this should work:

{#if $form.errors.email}
  <div>{$form.errors.email}</div>
{/if}

But, it does not. I can actually see the errors populated inside the data attribute of the div with id app too:

image

Is this expected behaviour or am I doing something wrong?

I consulted the readme, the corresponding pull request https://github.com/inertiajs/inertia-phoenix/pull/10 and even the other repo which another commenter mentioned previously at https://github.com/tonydangblog/phoenix-inertia-svelte. It seems to me like the $form object should be populated as per Inertia's own documentation at https://inertiajs.com/forms.

Again, thanks for the wonderful project. Thanks!

derrickreimer commented 3 months ago

Thanks for the question! I think this would fall in the purview of the Svelte adapter rather than the Phoenix server adapter. @tonydangblog by chance have you encountered any challenges with error propagation with Svelte?

thisistonydang commented 3 months ago

I just checked, and everything seems to be working as expected. $form.errors is populated when using assign_errors and returning an Inertia response.

@dsignr, I noticed that you are running your console.log in onMount, which only runs once and will not run again when the Inertia response returns. You can try adding a $: console.log("$form.errors:", $form.errors); to your component to see if the errors are populated.

In your template, if you read directly from $form.errors.email, it should work. Does it show up as undefined for you?

dsignr commented 3 months ago

I just checked, and everything seems to be working as expected. $form.errors is populated when using assign_errors and returning an Inertia response.

@dsignr, I noticed that you are running your console.log in onMount, which only runs once and will not run again when the Inertia response returns. You can try adding a $: console.log("$form.errors:", $form.errors); to your component to see if the errors are populated.

In your template, if you read directly from $form.errors.email, it should work. Does it show up as undefined for you?

Hi Tony, It correctly prints out when I changed the syntax, I guess it was the wrong usage of console.log that caused this. Thanks again!