Skip to content

useForm

A composable that provides comprehensive form handling capabilities including state management, submission handling, validation integration, and server error processing.

Usage

ts
import { ref } from 'vue'
import { useForm } from '#imports'

const form = ref(null) // Optional Vuetify form reference
const { model, submit, submitting, serverErrors } = useForm({
  initialValues: {
    name: '',
    email: '',
    password: '',
  },
  form, // Optional Vuetify form reference
  async onSubmit(values) {
    await api.post('users', values)
  },
})

Type Signature

ts
interface UseFormOptions<T> {
  initialValues: T;
  onSubmit: (values: T) => Promise<void>;
  form?: Ref<VForm>; // Vuetify form component reference
}

function useForm<T>(options: UseFormOptions<T>): {
  model: Ref<T>;
  updateModel: (value: T | undefined) => void;
  reset: () => void;
  submit: () => Promise<void>;
  submitting: Ref<boolean>;
  save: () => Promise<void>;
  loading: Ref<boolean>;
  resetValidation: () => void;
  serverErrors: ComputedRef<Record<keyof T, string>>;
  checkServerErrors: (error: any) => void;
  clearServerErrors: () => void;
  untrackedServerErrors: Ref<string[]>;
}

Parameters

ParameterTypeDescription
optionsUseFormOptions<T>Configuration options for the form
options.initialValuesTInitial values for the form model
options.onSubmit(values: T) => Promise<void>Function called when the form is submitted
options.formRef<VForm>Optional reference to a Vuetify form component for validation integration

Return Value

PropertyTypeDescription
modelRef<T>Reactive form data model
updateModel(value: T | undefined) => voidFunction to update the form model
reset() => voidReset form values and validation state
submit() => Promise<void>Handle form submission with validation
submittingRef<boolean>Whether form is currently being submitted
save() => Promise<void>Alias for submit
loadingRef<boolean>Alias for submitting
resetValidation() => voidClear validation errors
serverErrorsComputedRef<Record<keyof T, string>>Server-side validation errors
checkServerErrors(error: any) => voidParse server error response
clearServerErrors() => voidClear server validation errors
untrackedServerErrorsRef<string[]>Generic server errors not tied to specific fields

Features

  • Automatic validation: Integrates with Vuetify's form validation system
  • Server error handling: Processes API error responses into field-specific validation errors
  • Form state management: Tracks form submission state
  • Reset functionality: Easily reset form to initial values
  • Type safety: Fully typed form values and errors

Examples

Basic Form with Vuetify

vue
<template>
  <v-form ref="form">
    <v-row dense>
      <portal to="form-sites-title">
        <span v-if="isNew">{{ $t('sites.form.actions.create.title') }}</span>
        <span v-else>{{ $t('sites.form.actions.edit.title') }}</span>
      </portal>
      <v-col cols="12">
        <v-text-field
          v-model="model.name"
          :error-messages="serverErrors.name"
          :rules="rules.name"
          :label="$t('sites.form.fields.name.label')"
        />
      </v-col>

      <v-col cols="9">
        <v-text-field
          v-model="model.address"
          :error-messages="serverErrors.address"
          :rules="rules.address"
          :label="$t('sites.form.fields.address.label')"
        />
      </v-col>

      <v-col cols="3">
        <v-text-field
          v-model="model.address_number"
          :error-messages="serverErrors.address_number"
          :rules="rules.address_number"
          :label="$t('sites.form.fields.address_number.label')"
        />
      </v-col>

      <v-col cols="9">
        <v-text-field
          v-model="model.city"
          :error-messages="serverErrors.city"
          :rules="rules.city"
          :label="$t('sites.form.fields.city.label')"
        />
      </v-col>

      <v-col cols="3">
        <v-text-field
          v-model="model.province"
          :error-messages="serverErrors.province"
          :rules="rules.province"
          :label="$t('sites.form.fields.province.label')"
        />
      </v-col>
    </v-row>
    <portal to="form-sites-actions">
      <WeBtnDeleteConfirm
        v-if="!isNew"
        class="mr-2"
        :on-delete="deleteModel"
      />

      <v-btn
        color="primary"
        size="large"
        :loading="loading"
        @click="save"
      >
        <v-icon start>
          $save
        </v-icon> {{ $t('forms.buttons.save') }}
      </v-btn>
    </portal>
  </v-form>
</template>

<script setup lang="js">
const props = defineProps({
  extraParams: {
    type: Object,
    default: () => ({}),
  },
})

const emit = defineEmits(['deleted'])

const { t } = useI18n()

const defaultValues = {
  name: null,
  address: null,
  address_number: null,
  city: null,
  province: null,
}

function load(id) {
  const params = { with: '' }
  return useApi().$get(`sites/${id}`, { params })
}

defineExpose({ load })

const modelValue = defineModel('modelValue', { type: Object, default: undefined })
const id = computed(() => modelValue.value?.id)
const isNew = computed(() => !id.value)

const { validatorRequired } = useValidators()

const {
  save,
  loading,
  model,

  updateModel,
  resetValidation,
  serverErrors,
} = useForm({
  form: useTemplateRef('form'),
  initialValues: defaultValues,
  onSubmit,
})

const rules = computed(() => {
  return {
    name: [validatorRequired],
    address: [],
    address_number: [],
    city: [],
    province: [],
  }
})

watch(modelValue, (val) => {
  resetValidation()
  updateModel(val)
}, { immediate: true })

async function onSubmit() {
  const { ...values } = model.value

  const data = {
    ...props.extraParams,
    ...values,
  }

  const method = isNew.value ? 'post' : 'put'
  const url = isNew.value ? 'sites' : `sites/${id.value}`
  const res = await useApi().$request({ method, url, data })
  const message = isNew.value ? t('sites.form.messages.saveSuccess') : t('sites.form.messages.editSuccess')
  push.success(message)
  modelValue.value = res
}

async function deleteModel() {
  await useApi().$delete(`sites/${id.value}`)
  push.success(t('sites.form.messages.delete'))
  emit('deleted')
}
</script>

Source Code