ΠŸΠ΅Ρ€Π΅ΠΉΡ‚ΠΈ ΠΊ содСрТимому

🎯 ΠŸΡ€ΠΎΠ΄Π²ΠΈΠ½ΡƒΡ‚Ρ‹Π΅ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρ‹ ​

Условная валидация ​

typescript
createForm({ type: '', companyName: '' }, (r, define) =>
  define({
    type: r.required().oneOf(['personal', 'business']),
    companyName: r.requiredIf('type', 'business'),
  })
)

Асинхронная ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΈΠΌΠ΅Π½ΠΈ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ​

typescript
createForm({ username: '' }, (r, define) =>
  define({
    username: r
      .required()
      .minLength(3)
      .remote(
        async name => !(await fetch(`/api/users/${name}`)).ok,
        'Имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΡƒΠΆΠ΅ занято'
      ),
  })
)

Валидация Π΄ΠΈΠ°ΠΏΠ°Π·ΠΎΠ½Π° Π΄Π°Ρ‚ ​

typescript
createForm({ startDate: '', endDate: '' }, (r, define) =>
  define({
    startDate: r.required(),
    endDate: r.required().dateAfter('startDate'),
  })
)

Π£Π½ΠΈΠ²Π΅Ρ€ΡΠ°Π»ΡŒΠ½Π°Ρ Ρ„ΠΎΡ€ΠΌΠ° для создания ΠΈ рСдактирования ​

Одна ΠΈ Ρ‚Π° ΠΆΠ΅ Ρ„ΠΎΡ€ΠΌΠ° для создания ΠΈ рСдактирования. ΠšΠ»ΡŽΡ‡Π΅Π²ΠΎΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ β€” ΠΏΡ€ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ΅ Π΄Π°Π½Π½Ρ‹Ρ… ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ reset(), Π° Π½Π΅ setValues(), Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ baseline ΠΈ isDirty оставался false.

vue
<script setup lang="ts">
import { onMounted, computed } from 'vue'
import { createForm } from '@sakhnovkrg/vue-form-validator'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

const userId = computed(() =>
  route.params.id ? Number(route.params.id) : null
)
const isEditMode = computed(() => !!userId.value)

const form = createForm(
  {
    name: '',
    email: '',
    avatar: null as File | null,
  },
  (r, define) =>
    define({
      name: r.required().minLength(2),
      email: r.required().email(),
      avatar: [
        r.fileType(['.jpg', '.jpeg', '.png']),
        r.fileSize(3 * 1024 * 1024),
      ],
    }),
  {
    async onSubmit(values) {
      const formData = new FormData()
      formData.append('name', values.name)
      formData.append('email', values.email)
      if (values.avatar) formData.append('avatar', values.avatar)

      const url = isEditMode.value ? `/api/users/${userId.value}` : '/api/users'
      const method = isEditMode.value ? 'PUT' : 'POST'

      const response = await fetch(url, { method, body: formData })

      if (!response.ok) {
        const data = await response.json()
        form.setErrors(data.fieldErrors)
        return
      }

      const userData = await response.json()
      if (!isEditMode.value) {
        await router.push(`/users/${userData.id}/edit`)
      }
    },
  }
)

// Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Π΄Π°Π½Π½Ρ‹Ρ…: reset() обновляСт baseline, Ρ„ΠΎΡ€ΠΌΠ° остаётся чистой
onMounted(async () => {
  if (userId.value) {
    const { name, email } = await fetch(`/api/users/${userId.value}`).then(r =>
      r.json()
    )
    form.reset({ name, email })
  }
})
</script>

<template>
  <form @submit.prevent="form.submit">
    <input
      v-model="form.values.name"
      @blur="form.touch('name')"
      placeholder="Имя"
    />
    <span v-if="form.hasError('name')">{{ form.error('name') }}</span>

    <input
      v-model="form.values.email"
      @blur="form.touch('email')"
      placeholder="Email"
    />
    <span v-if="form.hasError('email')">{{ form.error('email') }}</span>

    <input type="file" @change="form.file.avatar.handler" />

    <button
      type="submit"
      :disabled="!form.isDirty || !form.isValid || form.isSubmitting"
    >
      {{
        form.isSubmitting
          ? 'Π‘ΠΎΡ…Ρ€Π°Π½Π΅Π½ΠΈΠ΅...'
          : isEditMode
            ? 'Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ'
            : 'Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ'
      }}
    </button>
  </form>
</template>

Установка ошибок полям ​

typescript
const form = createForm({ username: '', email: '' }, (r, define) =>
  define({
    username: r.required().minLength(3),
    email: r.required().email(),
  })
)

// Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ΠΎΡˆΠΈΠ±ΠΊΡƒ для ΠΎΠ΄Π½ΠΎΠ³ΠΎ поля
form.setErrors({ username: ['Π­Ρ‚ΠΎ имя ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ ΡƒΠΆΠ΅ занято'] })

// Π£ΡΡ‚Π°Π½ΠΎΠ²ΠΈΡ‚ΡŒ ошибки для Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… ΠΏΠΎΠ»Π΅ΠΉ
form.setErrors({
  username: ['НСдопустимыС символы Π² ΠΈΠΌΠ΅Π½ΠΈ'],
  email: ['Email ΡƒΠΆΠ΅ зарСгистрирован', 'НСвСрный Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ email'],
})

// ΠžΡ‡ΠΈΡΡ‚ΠΈΡ‚ΡŒ всС ошибки
form.resetErrors()

// ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ Π½Π°Π»ΠΈΡ‡ΠΈΠ΅ ошибки
if (form.hasError('username')) {
  console.log(form.error('username')) // ΠŸΠ΅Ρ€Π²Π°Ρ ошибка
  console.log(form.allErrors('username')) // ВсС ошибки поля
}

Π’ΠΈΠΏΠΈΡ‡Π½Ρ‹ΠΉ ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ сСрвСрных ошибок β€” Π²Π½ΡƒΡ‚Ρ€ΠΈ onSubmit:

typescript
const form = createForm(
  { email: '', username: '' },
  (r, define) =>
    define({ email: r.required().email(), username: r.required() }),
  {
    async onSubmit(values) {
      const res = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(values),
      })

      if (!res.ok) {
        // Π‘Π΅Ρ€Π²Π΅Ρ€ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚: { fieldErrors: { email: ['Π£ΠΆΠ΅ сущСствуСт'] } }
        const { fieldErrors } = await res.json()
        if (fieldErrors) form.setErrors(fieldErrors)
        return
      }

      console.log('Π‘ΠΎΠ·Π΄Π°Π½:', await res.json())
    },
  }
)

ΠžΠΏΡƒΠ±Π»ΠΈΠΊΠΎΠ²Π°Π½ΠΎ ΠΏΠΎΠ΄ Π»ΠΈΡ†Π΅Π½Π·ΠΈΠ΅ΠΉ MIT.