What's new in

vee-validate v4

Abdelrahman Awad

FE Engineer @

Creator of 

ee-validate

Hi 👋

I'm

Why are you using vee-validate?

  • Most popular (oldest 👴)
  • Declarative
  • Built-in rules
  • Error Messages

v2                   v3

0
 Advanced issue found
 
<input
  type="text"
  v-validate="'required'"
/>
<ValidationProvider
  rules="required"
  v-slot="{ errors }"
>
 <input
   type="text"
   v-model="input"
 />
</ValidationProvider>

Why?

  • Directives are stateless.
  • Directives are ideal for read-only logic.
  • Directives don't have access to the render logic.
  • And in Vue 3 they will no longer have access to the context component.

Current Problems

📉 Learning curve

🐢 Slow (Fast Enough)

🎯 Missed opportunities

😣 Verbose by default

vee-validate v4 Goals

✌ Vue 3 Support

✨ Better DX

⚡ Faster Forms

80% of Forms

  • Do not process values until submission
  • Use simple Inputs (Text, Select, Radio, etc...)
  • Validated before submission

Redundant Models

<template>
  <form>
    <input v-model="name" type="text">
    <input v-model="email" type="email">
    <input v-model="password" type="password">
  </form>
</template>

<script>
export default {
  data: () => ({
    name: '',
    email: '',
    password: ''
  })
};
</script>

👈 This is redundant

HOC Improvements

Do more with less

HOC Improvements

<ValidationObserver
  as="form"
  @submit="onSubmit"
  v-slot="{ errors }"
>
  
  <ValidationProvider
    name="email"
    as="input"
    type="email"
    rules="required|email" 
  />

  {{ errors.email }}

  <button>Submit</button>
</ValidationObserver>

Added `as` prop on both Provider and Observer components

HOC Improvements

<ValidationObserver
  as="form"
  @submit="onSubmit"
  v-slot="{ errors }"
>
  
  <ValidationProvider
    name="email"
    as="input"
    type="email"
    rules="required|email" 
  />

  {{ errors.email }}

  <button>Submit</button>
</ValidationObserver>

Direct access to errors from the observer slot

HOC Improvements

<ValidationProvider
    name="email"
    rules="required|email"
    v-slot="{ field, errors }">

  <input v-bind="field" type="text" />
  {{ errors[0] }}

</ValidationProvider>

v-model is no longer required

<ValidationProvider rules="required" name="birthday" v-slot="v">
  <v-menu
    ref="menu"
    v-model="menu"
    :close-on-content-click="false"
    :return-value.sync="date"
    transition="scale-transition"
    offset-y
    min-width="290px"
  >
    <template v-slot:activator="{ on }">
      <v-text-field
        v-model="date"
        label="Picker in menu"
        prepend-icon="event"
        readonly
        v-on="on"
      ></v-text-field>
    </template>
    <v-date-picker v-model="date" no-title scrollable>
      <v-spacer></v-spacer>
      <v-btn text color="primary" @click="menu = false">Cancel</v-btn>
      <v-btn text color="primary" @click="$refs.menu.save(date)">OK</v-btn>
    </v-date-picker>
  </v-menu>
</ValidationProvider>

Which node is an input? 😵

<ValidationProvider rules="required" name="birthday" v-slot="v">
  <VNode
    v-model="something"
  >
    <VNode v-model="something" />
    <VNode v-model="something" />
  </VNode>
</ValidationProvider>

This is how it looks like from our code POV

<ValidationProvider rules="required" name="birthday" v-slot="{ field }">
  <v-menu
    ref="menu"
    v-model="menu"
    :close-on-content-click="false"
    :return-value.sync="date"
    transition="scale-transition"
    offset-y
    min-width="290px"
  >
    <template v-slot:activator="{ on }">
      <v-text-field
        v-model="date"
        v-bind="field"
        label="Picker in menu"
        prepend-icon="event"
        readonly
        v-on="on"
      ></v-text-field>
    </template>
    <v-date-picker v-model="date" v-bind="field" no-title scrollable>
      <v-spacer></v-spacer>
      <v-btn text color="primary" @click="menu = false">Cancel</v-btn>
      <v-btn text color="primary" @click="$refs.menu.save(date)">OK</v-btn>
    </v-date-picker>
  </v-menu>
</ValidationProvider>

HOC Improvements

<template>
  <ValidationObserver as="form" @submit="onSubmit">
    <!-- Some Fields -->
  </ValidationObserver>
</template>

<script>
export default {
  setup() {
    const onSubmit = (values) => {
      // Send form values to the API
      // This will never run unless all fields are valid!
    };
    
    return {
      onSubmit
    };
  }
};
</script>

automatic validation before submission

Composition API

useField, useForm

useField

<template>
  <div>
    <input type="text" name="email" v-model="value">
    {{ errorMessage }}
  </div>
</template>

<script>
  import { useField } from 'vee-validate';

  export default {
    setup() {
      const { value, errorMessage } = useField('email', 'required|email');

      return {
        value,
        errorMessage
      };
    }
  };
</script>

Creates validatable models

useForm

<template>
  <div>
    <form @submit="onSubmit">
      <input name="name" v-model="name" type="text" />
      <span>{{ nameErrors[0] }}</span>

      <input name="email" v-model="email" type="email" />
      <span>{{ emailErrors[0] }}</span>

      <input name="password" v-model="password" type="password" />
      <span>{{ passwordErrors[0] }}</span>

      <button>Submit</button>
    </form>
  </div>
</template>

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

  export default {
    setup() {
      const { form, handleSubmit } = useForm();
      const { value: name, errors: nameErrors } = useField('name', 'required', { form });
      const { value: password, errors: passwordErrors } = useField('password', 'required|min:8', { form });
      const { value: email, errors: emailErrors } = useField('email', 'required|email', { form });
      const onSubmit = handleSubmit(values => {
        // Send data to the API
        console.log(values);
      });

      return {
        name,
        nameErrors,
        password,
        passwordErrors,
        email,
        emailErrors,
        onSubmit
      };
    }
  };
</script>

Creates a container for fields

So two ways to validate, which to pick?

Inline Rules

Defining Rules

import { extend } from 'vee-validate';

extend('required', (value) => {
  return !!value;
});

You can define them globally with the `extend` function

Defining Rules

import { useField } from 'vee-validate';

export default {
  setup() {
    const { value: password, errors: passwordErrors } = useField(
      'password',
      (value) => {
        if (!value) {
          return 'This is required';
        }

        if (value.length <= 8) {
          return 'This is too short';
        }

        return true;
      }
    );

    return { password, passwordErrors };
  }
};

You can pass validator functions to useField

Defining Rules

<template>
  <ValidationObserver as="form" v-slot="{ errors }">
    <ValidationProvider as="input" name="password" type="password" :rules="pwRules" />
    {{ errors.password }}
  </ValidationObserver>
</template>

<script>
  export default {
    setup() {
      const pwRules = (value) => {
        if (!value) {
          return 'This is required';
        }
        if (value.length <= 8) {
          return 'This is too short';
        }
        return true;
      };

      return { pwRules };
    }
  };
</script>

You can pass them to the rules prop

Defining Rules

import { useField } from 'vee-validate';
import * as yup from 'yup';

export default {
  setup() {
    const pwValidation = yup.string().required().min('8');
    const rules = async (value) => {
      try {
        await pwValidation.validate(value);
        return true;
      } catch (err) {
        return err.message;
      }
    }
    const { value, errors } = useField('password', rules);

    return {
      value,
      errors
    };
  }
};

Use any third party tools, like `yup`:

Defining Rules

import * as yup from 'yup';
import { useField } from 'vee-validate';
import { wrap } from '@vee-validate/yup';

export default {
  setup() {
    const pwValidator = wrap(yup.string().required().min('8'));
    const { value, errors } = useField('password', pwValidator);

    return {
      value,
      errors
    };
  }
};

Using `@vee-validate/yup`

Defining Rules as a Schema

import * as yup from 'yup';
import { wrapSchema } from '@vee-validate/yup';

export default {
  setup() {
    const schema = yup.object().shape({
      name: yup.string().required(),
      email: yup.string().email().required(),
      password: yup.string().min(8).required()
    });

    return {
      schema: wrapSchema(schema),
      onSubmit: (values) => {
        console.log(values);
      }
    }
  }
};

You can define rules at the form level

<ValidationObserver                  
    :validationSchema="schema"
    as="form" @submit="onSubmit"
    v-slot="{ errors }"
>
  <ValidationProvider
    name="name"
    as="input"
  />
  <span>{{ errors.name }}</span>

  <!-- Other fields... -->
</ValidationObserver>

ETA?

  • Check the progress of vee-validate v4 on the next branch.
  • Check the discussion on the PR here.
  • A new article will be released here

When its ready™

Thanks 👋

What's new in vee-validate v4

By Abdelrahman Awad

What's new in vee-validate v4

  • 1,067