Composition API Caveats

When using the composition API, there are a few things that are not clear when first starting to use it. This page will contain these topics and help you understand how to workaround or address them.

Function Field Names with useField

You might’ve noticed that some examples in the docs pass the field name as a function to useField:

jsimport { useField } from 'vee-validate';

export default {
  props: {
    name: String,
  },
  setup(props) {
    const { value, errorMessage } = useField(() => props.name);
  },
};

This is mainly because the props in Vue.js is a reactive object, meaning if you access or destruct any of its properties they will lose the reactivity aspect. Let’s say you did the following:

jsimport { useField } from 'vee-validate';

export default {
  props: {
    name: String,
  },
  setup(props) {
    // ❌ Don't do this in custom input components
    const { value, errorMessage } = useField(props.name);
  },
};

The implications are that vee-validate is no longer able to tell when the field name changes, which is crucial for syncing values when they do.

A common example where field names change frequently is in a array field where v-for loops field names use the index or the iterated value to generate the field name

vue<CustomTextField
  v-for="(user, idx) in users"
  :key="user.id"
  :name="`users[${idx}].name`"
></CustomTextField>

To address this issue, you need to get a reactive reference to the name property. Vue offers a few ways to do this, so you can use any of the following methods:

jsimport { toRef, toRefs, computed } from 'vue';

// ✅ using a function that returns the name
const { value, errorMessage } = useField(() => props.name);

// ✅ using `toRef`
const { value, errorMessage } = useField(toRef(props, 'name'));

// ✅ using `toRefs`
const { name } = toRefs(props);
const { value, errorMessage } = useField(name);

// ✅ using `computed`
const name = computed(() => props.name);
const { value, errorMessage } = useField(name);

Destructing composable

The composition API examples in the docs have always used the left-hand side destructing syntax when assigning component data with any of the composable functions vee-validate offers. To quickly refresh your memory:

jsconst { value } = useField('field');
const { handleChange } = useForm();
const { fields } = useFieldArray('users');

This is required by default, because each of these functions assumes you might never need the entire features each one provides and such each state/function is exposed independently as a Ref or a Function.

This means if you try the following, it won’t work as expected when used in your template:

vue<script setup>
import { useField, useForm, useFieldArray } from 'vee-validate';

export default {
  setup() {
    const form = useForm();
    const field = useField('field');
    const fieldArray = useFieldArray('users');

    return {
      form,
      field,
      fieldArray,
    };
  },
};
</script>

<template>
  <!-- ❌ Doesn't work -->
  <input v-model="field.value" />

  <!-- ❌ Doesn't work -->
  <pre>{{ form.meta.valid }}</pre>

  <!-- ❌ Doesn't work -->
  <div v-for="item in fieldArray.fields"></div>
</template>

This is because the setup function doesn’t recursively expose the refs inside of these objects. If you prefer to use the composition API like shown above, then you can fix most of the issues by wrapping the function calls with reactive().

vue<script setup>
import { reactive } from 'vue';
import { useField, useForm, useFieldArray } from 'vee-validate';

const form = reactive(useForm());
const field = reactive(useField('field'));
const fieldArray = reactive(useFieldArray('users'));
</script>

<template>
  <!-- ❌  Still Doesn't work because it is a writeable computed ref -->
  <input v-model="field.value" />

  <!-- ✅  Works if unwrapped manually -->
  <input v-model="field.value.value" />

  <!-- ✅  Works -->
  <pre>{{ form.meta.valid }}</pre>

  <!-- ✅  Works -->
  <div v-for="item in fieldArray.fields"></div>
</template>

You can read more on that behavior in Vue.js docs. Note that once you wrap the composable calls with reactive you no longer can destruct them and preserve the reactivity.