Как использовать ref и v -for в моем пользовательском компоненте во Vue 3?

У меня есть проверка поля, и во время отправки мне нужно сосредоточиться на первом поле ввода, в котором есть ошибки.

из моего родительского компонента CustomForm я не могу получить доступ к вводу дочернего компонента в CustomInput

<script setup lang = "ts">
import CustomForm from '@/components/CustomForm.vue'

</script>

<template>
  <CustomForm />
</template>

<style scoped lang = "scss">
</style>

<script setup lang = "ts">
import CustomInput from '@/components/CustomInput.vue'
import { useForm } from '@/hooks/useForm'

const formData = [
  {
    name: 'title',
    label: 'title',
    required: true,
    value: '',
    isValid: false,
    errorMessages: []
  },
  {
    name: 'name',
    label: 'name',
    required: true,
    type: 'textarea',
    value: '',
    isValid: false,
    errorMessages: []
  }
]
const { isFormValid, fieldsForm, submit } = useForm(formData)

const submitForm = () => {
  submit()
  if (isFormValid.value) {
    console.info('submit')
    console.info(fieldsForm)
  }
}


</script>

<template>
  <form @submit.prevent = "submitForm">
    <CustomInput v-for = "data in fieldsForm"
                 :key = "data.name"
                 ref = "customInputRef"
                 :field-data = "data"
                 v-model = "data.value"
                 v-model:error = "data.errorMessages"
                 v-model:isValid = "data.isValid"
    />
    <button type = "submit">Отправить</button>
  </form>
</template>

<style scoped lang = "scss">

</style>

<script setup lang = "ts">
import { computed, ref, watchEffect } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import type { IFieldProps } from '@/types/Field'
import { useInputValidator } from '@/hooks/useInputValidator'

const props = withDefaults(defineProps<IFieldProps>(), {
  placeholder: (props: IFieldProps) => `Input  ${props.fieldData.name.toUpperCase()} please`
})

const emit = defineEmits([
  'update:modelValue',
  'update:error',
  'update:isValid',
])
const r= ref(null)
const inputComponent = ref(props.fieldData.type !== 'textarea' ? 'input' : 'textarea')
const inputId = computed(() => `input-${uuidv4()}`)

const { isBlurred,field, blurAction,inputAction, errors, isValid } = useInputValidator(props.fieldData)

const inputHandler = (e: Event) => {
  const v = (e.target as HTMLInputElement).value
  emit('update:modelValue', v)
  inputAction()
  emit('update:error', errors)
}

const blurHandler = (e: Event) => {
  blurAction()
  emit('update:error', errors)
  emit('update:isValid', isValid)
}

</script>

<template>
  <div class = "field">
    <label class = "field__label"
           :for = "inputId">
      {{ field.label }}
    </label>
    <div class = "field__inner">
      <div class = "field-icon" v-if = "$slots.icon">
        <slot name = "icon"></slot>
      </div>
      <component :is = "inputComponent"
                 :name = "field.name"
                 ref = "r"
                 class = "field__input"
                 :class = "{
                   valid:field.isValid,
                   error:field.errorMessages.length,
                 }"
                 :id = "inputId"
                 :value = "field.value"
                 @input = "inputHandler"
                 @blur = "blurHandler"
                 :placeholder = "props.placeholder" />
      <template v-if = "field.errorMessages">
        <p class = "field__error" v-for = "(error,index) in field.errorMessages" :key = "index">
          {{ error }}
        </p>
      </template>
    </div>
  </div>
</template>
import type { Field } from '@/types/Field'
import { computed, ref } from 'vue'
import { validateField } from '@/helpers/validateField'


export const useForm = (formFields: Field[]) => {
  const fieldsForm = ref(formFields)
  const isFormValid = computed(() =>
    fieldsForm.value.every(field => field.isValid)
  )


  const updateValidity = (fieldName: string, errors: string[]) => {
    const field = fieldsForm.value.find(data => data.name === fieldName)
    if (field) {
      field.errorMessages = errors
    }
  }

  const checkFields = () => {
    fieldsForm.value.forEach(field => {
      let err: string[] = []
      if (field.required) {
        if (!isFormValid.value && !field.errorMessages.length) {
          err = validateField(field.name, field.value)
          updateValidity(field.name, err)
        }
      }
    })
  }

  const submit = () => {
    checkFields()
  }

  return {
    submit,
    isFormValid,
    fieldsForm

  }
}

import { computed, ref, watchEffect } from 'vue'
import type { Field } from '@/types/Field'
import { validateField } from '@/helpers/validateField'

export const useInputValidator = (fieldForm: Field) => {
  const field = ref<Field>(fieldForm)
  const errors = ref(field.value.errorMessages)
  const isBlurred = ref(false)
  const isValid = computed(() => {
   return !errors.value.length
  })

  watchEffect(()=>{
    if (field.value.errorMessages.length){
      isBlurred.value= true
      errors.value= field.value.errorMessages
    }
  })

  const inputAction = () => {
    if (isBlurred.value) {
      errors.value = validateField(field.value.name, field.value.value)
    }
  }

  const blurAction = () => {
    if (isBlurred.value) return
    errors.value = validateField(field.value.name, field.value.value)
    isBlurred.value =true
  }

  return {
    field,
    isValid,
    errors,
    blurAction,
    inputAction,
isBlurred
  }
}

Я проверил поля. Но функционал с фокусом остался. Я хотел бы иметь доступ к полям формы из хуков как изменить данные основного массива например isValid? .................................................. .................................................. ..

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
370
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Во-первых, чтобы применить Refs внутри v-for, используйте массив или функцию согласно документации.

Во-вторых, в дочернем компоненте используйте defineExpose() для привязки к требуемому элементу Ссылка на компонент.

Я создал простой пример:

/* App.vue */
<script setup>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';

const formData = ref({
  firstName: '',
  lastName: '', 
})

const inputs = ref({});

const focus = (key) => {
  inputs.value[key].input.focus();
}

</script>

<template>
  <div class = "inputs">
    <CustomInput
      v-for = "(value, key) in formData"
      v-model = "formData[key]"
      :ref = "el => inputs[key] = el"
      :label = "key"
    ></CustomInput>
  </div>
  <br>
  <div class = "btns">
    <button
      v-for = "(value, key) in inputs"
      @click = "focus(key)"
    >
      Focus to {{ key }}
    </button>
  </div>
  <br>
  <pre>{{ formData }}</pre>
</template>

<style>
.inputs {
  display: grid;
  gap: 4px;
}

.btns {
  display: flex;
  gap: 8px;
}
</style>
/* CustomInput.vue */

<script setup>
import { ref } from 'vue';

defineProps({
  label: String,
})

const model = defineModel();
const input = ref();

defineExpose({ input });

</script>

<template>
  <label>
    {{ label }}:
    <br>
    <input
      ref = "input"
      v-model = "model"
    >
  </label>
</template>

пожалуйста, помогите мне с моим кодом. Я сделал, как вы предложили. и я вхожу в консоль в свой родительский console.info(inputs.value), и он показывает мне, что существует массив {0: Proxy(Object), 1: Proxy(Object)}, но я не могу получить доступ к своему вводу. подскажите, как с учетом моего кода правильно реализовать функционал фокусировки на поле, где при отправке возникают ошибки? Я пытаюсь инкапсулировать логику в хуке useForm. Есть ли у вас предложения по улучшению моего кода?

Славик Гусев 28.02.2024 23:04

Привет! Если вы опубликуете пример рабочего кода, воспроизводящий проблему, я постараюсь помочь. В моем примере inputs.value — это объект типа {firstName: { input: HTMLInputElement }, lastName: { input: HTMLInputElement }}. То есть, если вы хотите сосредоточиться на первом поле: inputs.value.firstName.input.focus();

imhvost 29.02.2024 11:56

Я реализовал код, который в целом работает так, как мне нужно. но мне кажется, что есть лучший вариант. Скажи мне как?

import type { Field } from '@/types/Field'
import { computed, ref } from 'vue'
import type { Ref, UnwrapRef } from 'vue'
import { validateField } from '@/helpers/validateField'
import CustomInput from '@/components/CustomInput.vue'


export const useForm = (formFields: Field[],
                        inputs: Ref<UnwrapRef<InstanceType<typeof CustomInput>>>) => {
  const fieldsForm = ref(formFields)
  const isFormValid = computed(() =>
    fieldsForm.value.every(field => field.isValid)
  )

  const updateValidity = (fieldName: string, errors: string[]) => {
    const field = fieldsForm.value.find(data => data.name === fieldName)
    if (field) {
      field.errorMessages = errors
    }
  }
  const focus = () => {
    const arr = fieldsForm.value.findIndex((item) => {
      return item.errorMessages.length > 0
    })
    if (arr !== -1 && inputs.value[arr] && inputs.value[arr].input) {
      inputs.value[arr].input.focus()
    }
  }
  const checkFields = () => {
    fieldsForm.value.forEach((field) => {
      let err: string[] = []
      if (field.required) {
        if (!isFormValid.value && !field.errorMessages.length) {
          err = validateField(field.name, field.value)
          updateValidity(field.name, err)
        }
      }
    })
  }

  const submit = () => {
    if (isFormValid.value) return
    checkFields()
    focus()
  }

  return {
    submit,
    isFormValid,
    fieldsForm
  }
}
<script setup lang = "ts">
import CustomInput from '@/components/CustomInput.vue'
import { useForm } from '@/hooks/useForm'
import { ref, watchEffect } from 'vue'
import type { Field } from '@/types/Field'

const formData = [
  {
    name: 'title',
    label: 'title',
    required: true,
    value: '',
    isValid: false,
    errorMessages: []
  },
  {
    name: 'name',
    label: 'name',
    required: true,
    type: 'textarea',
    value: '',
    isValid: false,
    errorMessages: []
  }
]
const inputs = ref<InstanceType<typeof CustomInput>>([])
const { isFormValid, fieldsForm, submit } = useForm(formData, inputs)


const submitForm = () => {
  submit()
  if (isFormValid.value) {
    console.info('submit')
  }
}

</script>

<template>
  <form @submit.prevent = "submitForm">
    <CustomInput v-for = "(data,key) in fieldsForm"
                 :key = "data.name"
                 :ref = "(el:HTMLInputElement|null) => inputs[key] = el"
                 :field-data = "data"
                 v-model = "data.value"
                 v-model:error = "data.errorMessages"
                 v-model:isValid = "data.isValid"
    />
    <button type = "submit">Отправить</button>
  </form>
</template>

<style scoped lang = "scss">

</style>
<script setup lang = "ts">
import { computed, onUpdated, ref, watchEffect } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import type { IFieldProps } from '@/types/Field'
import { useInputValidator } from '@/hooks/useInputValidator'

const props = withDefaults(defineProps<IFieldProps>(), {
  placeholder: (props: IFieldProps) => `Input  ${props.fieldData.name.toUpperCase()} please`
})

const emit = defineEmits([
  'update:modelValue',
  'update:error',
  'update:isValid'
])

const inputComponent = ref(props.fieldData.type !== 'textarea' ? 'input' : 'textarea')
const inputId = computed(() => `input-${uuidv4()}`)

const { isBlurred, field, blurAction, inputAction, errors, isValid } = useInputValidator(props.fieldData)

const inputHandler = (e: Event) => {
  const v = (e.target as HTMLInputElement).value
  emit('update:modelValue', v)
  inputAction()
  emit('update:error', errors)
}

const blurHandler = (e: Event) => {
  blurAction()
  emit('update:error', errors)
  emit('update:isValid', isValid)
}
const input = ref<HTMLInputElement>()

defineExpose({
  input,
})

</script>

<template>
  <div class = "field">
    <label class = "field__label"
           :for = "inputId">
      {{ field.label }}
    </label>
    <div class = "field__inner">
      <div class = "field-icon" v-if = "$slots.icon">
        <slot name = "icon"></slot>
      </div>
      <component :is = "inputComponent"
                 ref = "input"
                 :name = "field.name"
                 class = "field__input"
                 :class = "{
                   valid:field.isValid,
                   error:field.errorMessages.length,
                 }"
                 :id = "inputId"
                 :value = "field.value"
                 @input = "inputHandler"
                 @blur = "blurHandler"
                 :placeholder = "props.placeholder" />
      <template v-if = "field.errorMessages">
        <p class = "field__error" v-for = "(error,index) in field.errorMessages" :key = "index">
          {{ error }}
        </p>
      </template>
    </div>
  </div>
</template>

Добро пожаловать в Stack Overflow. Пожалуйста, возьмите экскурсию . Это сайт вопросов и ответов, а не дискуссионный форум. Этот раздел предназначен только для ответов. Если вы хотите обновить свой вопрос, отредактируйте его.

Chris 01.03.2024 21:38

Другие вопросы по теме