Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shared errors format is incompatible with useForm's types #26

Open
enkot opened this issue Jun 19, 2024 · 2 comments
Open

Shared errors format is incompatible with useForm's types #26

enkot opened this issue Jun 19, 2024 · 2 comments

Comments

@enkot
Copy link

enkot commented Jun 19, 2024

Package version

1.1.0

Describe the bug

By default, Adonis returns validation errors for each field as an array of strings, but the useForm type for Inertia errors is Record<string, string> (https://github.com/inertiajs/inertia/blob/master/packages/vue3/src/useForm.ts#L10).
I think, in order to be compatible with useForm and because Adonis uses bail mode by default, it makes sense to provide users with a different error reporter to return one error message for each field.

Reproduction repo

No response

@enkot enkot changed the title Shared errors format are incompatible with useForm's types Shared errors format is incompatible with useForm's types Jun 19, 2024
@akatora28
Copy link

If it's helpful at all, I was able to modify the exception handler class to better support useForm with Vue and Vine.js validators. I'm still working through how best to implement this solution, but I've found the official docs to be pretty lacking on this topic at the moment.

In my /app/exceptions/handler.ts, import the vine errors and ValidationMessages types, and update the handler() method.

import app from '@adonisjs/core/services/app'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http'
import type { StatusPageRange, StatusPageRenderer } from '@adonisjs/core/types/http'

import { errors as vineErrors } from '@vinejs/vine'
import { ValidationMessages } from '@vinejs/vine/types'

export default class HttpExceptionHandler extends ExceptionHandler {
  /**
   * In debug mode, the exception handler will display verbose errors
   * with pretty printed stack traces.
   */
  protected debug = !app.inProduction

  /**
   * Status pages are used to display a custom HTML pages for certain error
   * codes. You might want to enable them in production only, but feel
   * free to enable them in development as well.
   */
  protected renderStatusPages = app.inProduction

  /**
   * Status pages is a collection of error code range and a callback
   * to return the HTML contents to send as a response.
   */
  protected statusPages: Record<StatusPageRange, StatusPageRenderer> = {
    '404': (error, { inertia }) => inertia.render('errors/not_found', { error }),
    '500..599': (error, { inertia }) => inertia.render('errors/server_error', { error }),
  }

  /**
   * The method is used for handling errors and returning
   * response to the client
   */
  async handle(error: unknown, ctx: HttpContext) {
      if (error instanceof vineErrors.E_VALIDATION_ERROR) {
        const errorsList = (error.messages as Array<ValidationMessages>).reduce(
          (accumulator, value: ValidationMessages) => {
            accumulator[value.field] = value.message
            return accumulator
          },
          {} as Record<string, string>
        )

        ctx.session.flash("errors", errorsList)
      }
      return super.handle(error, ctx)
  }

  /**
   * The method is used to report error to the logging service or
   * the a third party error monitoring service.
   *
   * @note You should not attempt to send a response from this method.
   */
  async report(error: unknown, ctx: HttpContext) {
    return super.report(error, ctx)
  }
}

Then in your vue component

<script setup lang="ts">
import { Head, usePage, useForm } from '@inertiajs/vue3'
import DashboardLayout from '~/layouts/dashboard_layout.vue'
import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { SubmitButton } from '@/components/ui/submit_button'

import InputError from '@/components/InputError.vue'

const form = useForm({
  name: '',
  uploadedFile: null as File | null,
})

const submitForm = () => {
  form.post(`/fileUploadEndpoint`, {
    forceFormData: true,
    onSuccess: () => {
      form.reset()
    },
    onError: (err) => {
      form.setError(err as any)
    }
  })
}
</script>

<template>
  <Head title="New Project" />
  <DashboardLayout pageTitle="New Project">
    <template #actions></template>
    <form @submit.prevent="submitForm"  enctype="multipart/form-data" class="grid w-full items-center gap-2">
      <div>
        <Label for="name">File Name</Label>
        <Input v-model="form.name" id="name" type="text" placeholder="MyFile.txt" />
        <InputError :error="form.errors.name ? form.errors.name[0] : null"  />
      </div>
      <div>
        <Label for="uploadedFile">Upload File</Label>
        <Input id="uploadedFile" type="file" @change="e => form.uploadedFile = e.target.files[0]" />
          <InputError :error="form.errors.uploadedFile ? form.errors.uploadedFile[0] : null"  />
      </div>
      <SubmitButton :processing="form.processing">Create New File</SubmitButton>
    </form>
  </DashboardLayout>
</template>
</script>

@aryanjaya
Copy link

I found another solution for this.

in config/inertia.ts, you can modify validation errors directly in sharedData section and retrieve only the first message from each key.

import { defineConfig } from '@adonisjs/inertia'
import type { InferSharedProps } from '@adonisjs/inertia/types'

const inertiaConfig = defineConfig({
  /**
   * Path to the Edge view that will be used as the root view for Inertia responses
   */
  rootView: 'inertia_layout',

  /**
   * Data that should be shared with all rendered pages
   */
  sharedData: {
    errors: (ctx) => {
      const errors: undefined | Record<string, string[]> = ctx.session?.flashMessages.get('errors')

      if (errors === undefined) {
        return errors
      }

      const errorList: Record<string, string> = {}

      for (const [key, value] of Object.entries(errors)) {
        errorList[key] = value[0]
      }

      return errorList
    },
  },

  /**
   * Options for the server-side rendering
   */
  ssr: {
    enabled: false,
    entrypoint: 'inertia/app/ssr.ts',
  },
})

export default inertiaConfig

declare module '@adonisjs/inertia/types' {
  export interface SharedProps extends InferSharedProps<typeof inertiaConfig> {}
}

and that's it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants