Search code examples
vue-componentvuejs3

How to access all refs in component and call their exposed method in Vue 3?


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:

  1. I created validation that runs inside of my formElement component every time I focus out of input. I also exposed it so I can call it from parent component by ref. This works fine.

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>
  1. For the form validation I defined ref on form and also ref for each formElement.

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.


Solution

  • 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>