I created validation that runs inside of my formElement component every time I focus out of input.
Now I would like to make extra step and validate whole form on submit before sending data to server. For this I can expose the validation method on formElement and call it from ref variable in parent. This works fine.
The problem is I cannot wrap my head around how to do this without defining ref variable in template and in script for every formElement I have. My goal is to define one main ref on form and then iterate over child refs and call validate method on each component. How can I achieve this?
So far I tried this:
Very simplified example of formElement:
<template>
<div ref="formElement">
...
<input :value="" @focusout="validate"/>
...
</div>
</template>
<script setup>
...
function validate() {
// do your stuff here
}
...
defineExpose({
validate
});
</script>
My approach looks like this (again very simplified example of parent component with form):
<template>
<form ref="form">
<form-element ref="name"></form-element>
<form-element ref="surname"></form-element>
<form-element ref="age"></form-element>
<button @click="validateAndSubmit">
submit
</button>
</form>
</template>
<script setup>
...
const form = ref(null);
const name = ref(null);
function validateAndSubmit() {
form.value.name.validate(); // this is not working
form.value.name.value; // this will give me value of input from DOM but I dont need it in the scope of what I am trying to achieve
form.value.name.value.validate(); // this is not working
name.value.validate(); // this is working, but I would have to define all refs that I have in template which is not scalable
// do other logic
}
...
</script>
Now if I remember correctly, in Vue 2 it was possible to get all refs with $refs variable, but looks like I dont have this option in Vue 3.
Use Refs inside v-for
or Function Refs
Check also this answer for the solution.
Here is the sample doing it both ways.
const { createApp, ref } = Vue;
const FormElement = {
props: ['id'],
template: '{{id}}: <input :id="id" /><br/>',
methods: {
validate() { console.log('validate(): FormElement ' + this.id) }
}
}
const App = {
components: { FormElement },
setup() {
const refs = ref([])
const validate = () => {
console.log('validate(): Form')
refs.value.map(fe => fe.validate())
}
const setRef = (el) => {
refs.value.push(el)
}
return { refs, validate, setRef }
}
}
const app = createApp(App)
app.mount('#app')
#app { line-height: 2; }
[v-cloak] { display: none; }
<div id="app" v-cloak>
<form-element v-for="n in 3" ref="refs" :id="n" :key="n"></form-element>
<form-element id="4" :ref="setRef"></form-element>
<form-element id="5" :ref="setRef"></form-element>
<button @click="validate">validate</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>