Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — Validation Behavior and Error Messages

Form validation is an important part of any app.

In this article, we’ll look at how to use Vee-Validate 4 in our Vue 3 app for form validation.

Validation Behavior

Vee-Validate runs validate when the change event is emitted or when the value is changed externally.

Customizing Validation Triggers

We can change when fields are validated.

To do this, we use the configure function to change this behavior:

<template>
  <Form @submit="submit" :validation-schema="schema" v-slot="{ errors }">
    <Field name="email" as="input" />
    <span>{{ errors.email }}</span>
    <br />
    <Field name="password" as="input" type="password" />
    <span>{{ errors.password }}</span>
    <br />
    <button>Submit</button>
  </Form>
</template>
<script>
import { configure, Field, Form } from "vee-validate";  `
import * as yup from "yup";
configure({
  validateOnBlur: true,
  validateOnChange: true,
  validateOnInput: false,
  validateOnModelUpdate: true,
});
export default {
  components: {
    Field,
    Form,
  },
  data() {
    const schema = yup.object().shape({
      email: yup.string().required().email(),
      password: yup.string().required().min(8),
    });
    return {
      schema,
    };
  },
  methods: {
    submit() {},
  },
};
</script>

validateOnBlur means validation will run when the blur event is emitted.

validateOnChange means validation will run when the change event is emitted.

validateOnInput means validation runs when the input event is emitted.

And validateInModelUpdate means validation runs when the model reactive properties updates.

Displaying Error Messages

We can create our own field and display the error messages by using the slot props.

For instance, we can write:

<template>
  <Form @submit="submit" :validation-schema="schema">
    <Field :rules="rules" v-slot="{ field, errors, errorMessage }" name="email">
      <input v-bind="field" type="text" />
      <p>{{ errors[0] }}</p>
      <p>{{ errorMessage }}</p>
    </Field>
    <br />
    <button>Submit</button>
  </Form>
</template>
<script>
import { Field, Form } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Form,
    Field,
  },
  data() {
    const schema = yup.object().shape({
      email: yup.string().required().email(),
    });

    return {
      schema,
    };
  },
  methods: {
    submit() {},
  },
};
</script>

We have the validation-schema prop with the validation schema.

And in the Field component, we have the input element.

We pass in the field properties as props to make the validation trigger on the input.

And we show the errors with the errors[0] or errorMessage expressions.

Also, we can display all the error messages for the fields by iterating over the errors object:

<template>
  <Form v-slot="{ errors }" :validation-schema="schema">
    <template v-if="Object.keys(errors).length">
      <p>Please correct the following errors</p>
      <ul>
        <li v-for="(message, field) in errors" :key="field">
          {{ message }}
        </li>
      </ul>
    </template>

    <Field name="name" as="input" :rules="rules" />
    <Field name="email" as="input" :rules="rules" />
    <Field name="password" as="input" :rules="rules" />
  </Form>
</template>
<script>
import { Field, Form } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Field,
    Form,
  },
  data() {
    const schema = yup.object().shape({
      email: yup.string().required().email(),
      password: yup.string().required().min(8),
      name: yup.string().required(),
    });
    return {
      schema,
    };
  },
  methods: {
    submit() {},
  },
};
</script>

We get the keys from the errors object and loop through them with the v-for directive.

The message is the value, and we display them in the li .

Conclusion

We can change when validation is run and display error messages our way in our Vue 3 app with Vee-Validate 4.

Categories
Vue 3

Form Validation in a Vue 3 App with Vee-Validate 4 — Validation Schema

Form validation is an important part of any app.

In this article, we’ll look at how to use Vee-Validate 4 in our Vue 3 app for form validation.

Field Level Validation

We can validate fields with the Field component.

For instance, we can write:

<template>
  <Form v-slot="{ errors }">
    <Field name="field" as="input" :rules="isRequired" />
    <br />
    <span>{{ errors.field }}</span>
  </Form>
</template>
<script>
import { Field, Form } from "vee-validate";
export default {
  components: {
    Field,
    Form,
  },
  methods: {
    isRequired(value) {
      return value ? true : "This field is required";
    },
  },
};
</script>

We have the Field component which has the as prop to set the tag name of the element to render.

rules has the field validation function.

The isRequired function returns true if the value is valid and a validation error message otherwise.

value has what we inputted.

We can also use 3rd party libraries for validation.

For example, we can use the yup library for validation by writing:

<template>
  <Form v-slot="{ errors }">
    <Field name="field" as="input" :rules="passwordRules" />
    <br />
    <span>{{ errors.field }}</span>
  </Form>
</template>
<script>
import { Field, Form } from "vee-validate";
import * as yup from "yup";

export default {
  components: {
    Field,
    Form,
  },
  data() {
    return {
      passwordRules: yup.string().required().min(8),
    };
  },
};
</script>

We have the passwordRules reactive property which is set to yup.string().required().min(8) to validate that our inputted value is a string with a minimum of 8 characters.

Form-Level Validation

We can also add form level validation with Vee-Validate 4.

For instance, we can write:

<template>
  <Form @submit="submit" :validation-schema="simpleSchema" v-slot="{ errors }">
    <Field name="email" as="input" />
    <span>{{ errors.email }}</span>
    <br />
    <Field name="password" as="input" type="password" />
    <span>{{ errors.password }}</span>
    <br />
    <button>Submit</button>
  </Form>
</template>
<script>
import { Field, Form } from "vee-validate";
export default {
  components: {
    Field,
    Form,
  },
  data() {
    const simpleSchema = {
      email(value) {
        return /S+@S+.S+/.test(value) ? true : "email is not valid";
      },
      password(value) {
        return value || "password is required";
      },
    };
    return {
      simpleSchema,
    };
  },
  methods: {
    submit() {},
  },
};
</script>

We set the validation-schema to the simpleSchema object which has the validation functions for each field we want to validate.

The function names in simpleSchema should match the value of the name prop of the Field component.

Validation Schemas with Yup

We can add validation schemas with yup .

To do this, we write:

<template>
  <Form @submit="submit" :validation-schema="schema" v-slot="{ errors }">
    <Field name="email" as="input" />
    <span>{{ errors.email }}</span>
    <br />
    <Field name="password" as="input" type="password" />
    <span>{{ errors.password }}</span>
    <br />
    <button>Submit</button>
  </Form>
</template>
<script>
import { Field, Form } from "vee-validate";
import * as yup from "yup";
export default {
  components: {
    Field,
    Form,
  },
  data() {
    const schema = yup.object().shape({
      email: yup.string().required().email(),
      password: yup.string().required().min(8),
    });
    return {
      schema,
    };
  },
  methods: {
    submit() {},
  },
};
</script>

We create the schema object with the email and password properties, which match the name attribute values on the Field component.

We call the yup.object.shape method to create the schema with the email and password fields to do the validation for each field.

Conclusion

We can use the Yup library with Vee-Validate to validate our form fields.

Categories
Vue 3

Getting Started with Form Validation in a Vue 3 App with Vee-Validate 4

Form validation is an important part of any app.

In this article, we’ll look at how to use Vee-Validate 4 in our Vue 3 app for form validation.

Getting Started with Script Tag

We can get started with the script tag.

We use the v-form component as the form container.

The v-field component is our input field.

error-message component is our error message.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vee-validate@next"></script>
  </head>
  <body>
    <div id="app">
      <v-form @submit="onSubmit">
        <v-field
          name="name"
          as="input"
          type="text"
          placeholder="What's your name?"
          :rules="isRequired"
        ></v-field>
        <br />
        <error-message name="name"></error-message>
        <br />
        <button>Submit</button>
      </v-form>
    </div>
    <script>
      const app = Vue.createApp({
        components: {
          VForm: VeeValidate.Form,
          VField: VeeValidate.Field,
          ErrorMessage: VeeValidate.ErrorMessage
        },
        methods: {
          isRequired(value) {
            if (!value) {
              return "this field is required";
            }
            return true;
          },
          onSubmit(values) {
            alert(JSON.stringify(values, null, 2));
          }
        }
      });
      app.mount("#app");
    </script>
  </body>
</html>

First, we add the script tag with:

<script src="https://unpkg.com/vee-validate@next"></script>

Then we register the VForm , VField , and ErrorMessage components so we can use them in our template.

We have the isRequired method which we use for the rule for the form by passing it into the rules prop.

It returns true if the form field is valid.

Otherwise, it returns the error message we want to display.

value has the value that we entered.

The v-field component has the as prop which is set to input to render it as an input box.

The error-message has the name which would match the value of the name attribute of the field that we’re validating.

Getting Started with NPM Package

We can install the Vee-Validate NPM package by running:

yarn add vee-validate@next

or

npm i vee-validate@next --save

Then we can use it by writing:

<template>
  <Form v-slot="{ errors }">
    <Field name="field" as="input" :rules="isRequired" />
    <br />
    <span>{{ errors.field }}</span>
  </Form>
</template>

<script>
import { Field, Form } from "vee-validate";

export default {
  components: {
    Field,
    Form,
  },
  methods: {
    isRequired(value) {
      return value ? true : "This field is required";
    },
  },
};
</script>

We register the Field and Form components so we can use them in our template.

Form is the form wrapper. It has the errors property from the slot props with the error message.

The Field component is like the v-field component we have in the previous example.

errors.field has the validation form validation error message of the 'field' field.

isRequired function has the same validation logic as the previous example.

It returns true if value is valid and an error validation message otherwise.

Conclusion

We can add simple form validation into our Vue 3 app with Vee-Validate 4.

Categories
Vue 3

Vue 3 — Mixins

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to create mixins to make our code reusable.

Mixins

Mixins are a flexible way to let us add reusable functionalities to Vue components.

A mixin object can have any component options.

When a component uses a mixin, the mixin will be merged into the component’s own options.

For example, we can create a mixin and use it by writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <p>app</p>
    </div> <script>
      const mixin = {
        created() {
          this.foo();
        },
        methods: {
          foo() {
            console.log("foo");
          }
        }
      }; const app = Vue.createApp({
        mixins: [mixin]
      }); app.mount("#app");
    </script>
  </body>
</html>

We called Vue.createApp with an object that has the mixins property.

The value is our mixin mixin.

It’s just an object that has the component properties in the places we expect.

They’ll be merged into the component automatically.

Therefore, the this.foo method will be called and we’ll see the console log run.

Option Merging

The options will be merged using the appropriate strategies.

The component’s data take priority over the mixin in case there are any conflicts.

Hook functions with the same name are merged into an arraty so that all of them will be called.

So if we have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <p>app</p>
    </div> <script>
      const mixin = {
        created() {
          console.log("mixin hook");
        }
      }; const app = Vue.createApp({
        mixins: [mixin],
        created() {
          console.log("app hook");
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

Then we have the 'mixin hook' and 'app hook' logged in that order.

Options that expect object values like methods , components , and directives will be merged into the same object.

The component’s option will take priority when there’re conflicting keys.

So if we have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <p>app</p>
    </div> <script>
      const mixin = {
        methods: {
          foo() {
            console.log("foo");
          },
          conflicting() {
            console.log("mixin conflicting");
          }
        }
      }; const app = Vue.createApp({
        mixins: [mixin],
        methods: {
          baz() {
            console.log("bar");
          },
          conflicting() {
            console.log("app conflicting");
          }
        }
      }); const vm = app.mount("#app");
      vm.conflicting();
      vm.foo();
      vm.baz();
    </script>
  </body>
</html>

Then we’ll see:

app conflicting
foo
baz

logged.

The Vue instance’s conflicting method is used over the mixin one.

But foo and baz are incorporated into the Vue instancem since there’re no conflict between them.

Global Mixin

We can create global mixins with the app.mixin method.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <p>app</p>
    </div> <script>
      const app = Vue.createApp({
        foo: "bar"
      }); app.mixin({
        created() {
          const foo = this.$options.foo;
          console.log(foo);
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

We added the foo property to our Vue instance.

This is accessed with the this.$options property in our mixin.

So we’ll see 'bar' logged from our created hook.

Once we apply a mixin globally, then it’ll affect every Vue instance created afterwards.

Conclusion

Mixins are reusable pieces of code that can be incorporated into our Vue components.

Categories
Vue 3

Vue 3 — Dynamic Transitions and State Animations

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at creating dynamic transitions and state change animation.

Dynamic Transitions

We can create dynamic transitions by passing in a dynamic string to the name prop.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
  </head>
  <body>
    <div id="app">
      Fade in
      <input type="range" v-model="fadeDuration" min="0" :max="5000" /> <button @click="show = !show">
        toggle
      </button>
      <transition
        :css="false"
        @before-enter="beforeEnter"
        @enter="enter"
        @leave="leave"
      >
        <p v-if="show">hello</p>
      </transition>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            show: false,
            fadeDuration: 1000
          };
        },
        methods: {
          beforeEnter(el) {
            el.style.opacity = 0;
          },
          enter(el, done) {
            Velocity(
              el,
              { opacity: 1 },
              {
                duration: this.fadeDuration,
                complete: done
              }
            );
          },
          leave(el, done) {
            Velocity(
              el,
              { opacity: 0 },
              {
                duration: this.fadeDuration,
                complete: done
              }
            );
          }
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

We added the Velocity library to add transitions effects of our choice.

The fadeDuration can be adjusted so that we change the length of the enter and leave effects.

We just called Velocity to change the opacity to what we want.

State Transitions

Vue can animate state changes.

They include things like numbers and calculations, colors displayed, SVG nodes, and other properties.

To do that, we can animate state change with watches.

For instance, we can make a simple state transition by writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model.number="number" type="number" step="20" />
      <p>{{ animatedNumber }}</p>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            number: 0,
            tweenedNumber: 0
          };
        },
        computed: {
          animatedNumber() {
            return this.tweenedNumber.toFixed(0);
          }
        },
        watch: {
          number(newValue) {
            gsap.to(this.$data, { duration: 1.5, tweenedNumber: newValue });
          }
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

When we type in a number, then it’ll be animated.

The animation is don’t in the number watcher.

We call Greensock’s to method to animate towards the final value.

this.$data has the value of the final number.

tweenedNumber is the value displayed as the animation is being done.

animatedNumber takes the tweenedNumber value, return it and display it.

Organizing Transitions into Components

We can organize animations into their own components.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model.number="x" type="number" step="20" /> +
      <input v-model.number="y" type="number" step="20" /> = {{ result }}
      <p>
        <animated-integer :value="x"></animated-integer> +
        <animated-integer :value="y"></animated-integer> =
        <animated-integer :value="result"></animated-integer>
      </p>
    </div> <script>
      const app = Vue.createApp({
        data() {
          return {
            x: 400,
            y: 373
          };
        },
        computed: {
          result() {
            return this.x + this.y;
          }
        }
      }); app.component("animated-integer", {
        template: "<span>{{ fullValue }}</span>",
        props: {
          value: {
            type: Number,
            required: true
          }
        },
        data() {
          return {
            tweeningValue: 0
          };
        },
        computed: {
          fullValue() {
            return Math.floor(this.tweeningValue);
          }
        },
        methods: {
          tween(newValue, oldValue) {
            gsap.to(this.$data, {
              duration: 0.5,
              tweeningValue: newValue,
              ease: "sine"
            });
          }
        },
        watch: {
          value(newValue, oldValue) {
            this.tween(newValue, oldValue);
          }
        },
        mounted() {
          this.tween(this.value, 0);
        }
      }); app.mount("#app");
    </script>
  </body>
</html>

We created the animated-integer component to animate our number.

It still animates the number, but we have a tween method to create the animation so that we can simplify our value watcher method.

fullValue is a computed property to round this.tweeningValue down to the nearest integer.

this.tweeningValue is the values that’ll be displayed during the animation.

So when we type in our numbers, we’ll see all the numbers being animated.

Conclusion

One good use of watchers is to animate state changes.

We made this easy with the Greensock library.

Also, we can change the settings of the transition on the fly.