Skip to content

Asynchronous form handling and validation for VueJS forms

License

Notifications You must be signed in to change notification settings

crrobinson14/vue-smooth-form

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vue-smooth-form

Asynchronous form handling library for VueJS.

Alternatives

This module was inspired by https://github.com/jaredpalmer/formik and https://github.com/blocka/vue-simpleform. Formik is the well-known form handling library for React, and vue-simpleform is its closest counterpart for VueJS. However, it has two problems that prevented me (and possibly you) from using it:

  1. It didn't work with custom form controls such as those in Vuetify, the Material-Design component library for Vue. Many Vuetify form controls pass raw strings in their events, and vue-simpleform is hard-coded to expect HTML DOM Event objects for all event callbacks.

  2. I would have filed a pull request to fix this, but vue-simpleform appears to be no longer actively maintained. There are two open pull requests that have not been addressed and it is still in its 0.0.13 version at this time.

Basic Usage

<template>
  <vue-smooth-form v-slot="{form}" :initial-values="initialValues" :validate-form="validate">
    <!-- Vuetify controls pass raw strings in their event handlers. Use this syntax for passing along their values. -->
    <v-text-field
      type="email"
      @blur="form.onBlur"
      @change="form.onChange('email', $event)"
      @input="form.onInput('email', $event)"
      name="email"
      :value="form.values.email"
      :error-messages="form.touched.email && form.errors.email || ''"
      autocomplete="email"
      label="Email address..."/>

    <!-- Standard HTML form control events can be passed directly -->
    <input 
      type="password" 
      @blur="form.onBlur"
      @change="form.onChange"
      @input="form.onInput"
      name="password" 
      :value="form.values.password" />

    <v-btn color="primarylight" :disabled="form.isSubmitting || !form.isValid" @click.prevent="login(form)">Login</v-btn>
    <v-btn color="primarylight" :disabled="form.isSubmitting || !form.isValid" @click.prevent="form.resetForm">Reset</v-btn>

    <v-btn flat :disabled="form.submitting">Forgot Password</v-btn>
  </vue-smooth-form>
</template>

<script>
    import VueSmoothForm from 'vue-smooth-form';
    import API from './api';
    
    export default {
        components: { VueSmoothForm },
        data () {
            return {
                initialValues: {
                    email: '',
                    password: '',
                }
            }
        },
        methods: {
            // Form action handlers can be whatever we want. 
            async login(form) {
                form.setSubmitting(true);

                try {
                    const result = await API.login(form.values);
                    console.log('login result', result);
                    form.resetForm();
                } catch (e) {
                    alert(e.message);
                }
              
                form.setSubmitting(false);
            },
        
            // The validation routine receivs a reference to the form. We destructure here and pull out the only properties we need.
            // values contains the field values, and setFormErrors is a utility method to set all the error states on the form.
            async validate({ values, setFormErrors }) {
              if (this.validationAbort) {
                this.validationAbort.abort();
                this.validationAbort = null;
              }
        
              this.validationAbort = new AbortController();
        
              try {
                const result = await API.validateForm('login', values, this.validationAbort.signal);
                if (result) {
                  // We can arrive here with a null result if we aborted a request
                  setFormErrors(result.errors);
                }
              } catch (e) {
                console.log('Validation error', e);
              }
        
              this.validationAbort = null;
            },
        },
    }
</script>

Details

This library provides a simple wrapper component called vue-smooth-form. It initializes a local values object from a set of initial values provided by the parent component, then passes a reference to itself (with useful methods, properties, and events) to its children via a slot binding. Simply wire each input field to the values, errors, and event handlers via that reference.

Input Field Event Handlers

form.onBlur - Connect this to the @blur event for each input field. Sets the field to "touched". form.onChange - Connect this to the @change event for each input field. Sets the field to "dirty". form.onInput - Connect this to the @input event for each input field. Sets the field to "touched" and "dirty".

Note that all three event handlers may be called with one of two patterns:

form.onChange(fieldName, value) - For cases where you have a raw string or other value to pass form.onChange($event) - For cases where you have an HTML DOM event. The field name will be taken from event.target.name, so ensure that your input field has a name attribute.

Form Properties

form.values - { field: error } map of current field values form.errors - { field: error } map of error strings as set by the validation function

form.isValid - True if the entire form is valid (no fields have errors). Just a shortcut for checking if the errors object is empty. form.isSubmitting - True if the form is being submitted, as set by form.setSubmitting(). Useful for disabling buttons.

form.valid - { field: isValid } map of booleans identifying valid fields (those with no errors) form.invalid - { field: isValid } map of booleans identifying invalid fields form.untouched - { field: isValid } map of booleans identifying untouched fields (those that have never received focus)
form.touched - { field: isValid } map of booleans identifying touched fields form.pristine - { field: isValid } map of booleans identifying pristine fields (those that have not been modified) form.dirty - { field: isValid } map of booleans identifying dirty fields

Support Methods

form.setSubmitting(true|false) - Set the isSubmitting status for the form form.resetForm() - Reset all values{} back to initialValues{}. form.setFieldValue(path, value) - Set field to value. form.setFieldError(path, error) - Set the error status for a field. Set error to null to clear an error. form.setFieldTouched(path, true|false) - Set the touched and untouched statuses for a field form.setFieldDirty(path, true|false) - Set the dirty and pristine statuses for a field form.setFormErrors(errors) - Set all errors on a form. The parameter should be a dictionary of fields with error strings or null.

Validation and Submission

Unlike other form handling libraries, vue-smooth-form does not try to handle submit events for you. Just create whatever buttons, links, or other controls you want to handle this. Typically you would use the form.setSubmitting() method as shown above, but only as a convenience if you want to disable certain controls (like the submit button) during the submit call to the server.

Validation is done automatically whenever a field's value changes, or the field loses focus. Validation may be asynchronous, and vue-smooth-form will wait for it to complete. However, note that rapid data entry by the user may trigger this callback many times. If you want to be kind to your server, consider using a cancelable request using AbortController. An example of this is shown above.

Yup Schema

To reduce boilerplate, you can use the excellent Yup library to define a schema. If you do, VueSmoothForm can handle all of the validation for you with no callbacks:

<template>
    <vue-smooth-form v-slot="{form}" :initial-values="initialValues" :yup-schema="validationSchema">
        ...
    </vue-smooth-form>
</template>

<script>
import VueSmoothForm from 'vue-smooth-form';
import * as yup from 'yup';

export default {
  components: { VueSmoothForm },
  props: [],
  data: function() {
    return {
      initialValues: {
        name: '',
        email: '',
      },
      validationSchema: yup.object().shape({
        name: yup.string()
          .min(3, 'Please enter between 3 and 20 characters')
          .max(20, 'Please enter between 3 and 20 characters')
          .required('This field is required'),
        email: yup.string()
          .email('Please enter a valid email address')
          .required('This field is required'),
      })
    }
  },
}
</script>

Event Handling

VueSmoothForm needs to know about events occurring in input fields to update its values and other internal state. But sometimes you may also want to know about these changes. A good example is if you need to clear one field when another is changed. In the form below, we show a contact method selector and room to enter an email address or phone number. If we select by-email, we disable the phone number field, and vice versa. But it could be confusing for the user to see a value still in the disabled field (which they cannot clear because of it being disabled). Using the change event we clear this for them:

<template>
    <vue-smooth-form v-slot="{form}" :initial-values="initialValues" :yup-schema="validationSchema" v-on:value="onValue">
        <select
            id="contactMethod"
            name="contactMethod"
            @blur="form.onBlur"
            @change="form.onChange"
            @input="form.onInput"
            :value="form.values.contactMethod">
            <option value="email">Email</option>
            <option value="phone">Phone</option>
          </select>

        <input
          id="email"
          type="text"
          @blur="form.onBlur"
          @change="form.onChange"
          @input="form.onInput"
          name="email"
          :value="form.values.email"
          :disabled="form.values.contactMethod !== 'email"
          :placeholder="Email Address">

        <input
          id="phone"
          type="text"
          @blur="form.onBlur"
          @change="form.onChange"
          @input="form.onInput"
          name="phone"
          :value="form.values.phone"
          :disabled="form.values.contactMethod !== 'phone"
          :placeholder="Phone Number">

    </vue-smooth-form>
</template>

<script>
import VueSmoothForm from 'vue-smooth-form';

export default {
  components: { VueSmoothForm },
  props: [],
  data: function() {
    return {
      initialValues: {
        contactMethod: 'email',
        email: '',
        phone: '',
      },
    }
  },
  methods: {
    onValue({ path, value, form }) {
      console.log(`Changed ${path} to ${value}`);
      if (path === 'contactMethod') {
        const unusedField = value === 'email' ? 'phone' : 'email';
        form.setFieldValue(unusedField, '');
      }
    },
  }
}
</script>

About

Asynchronous form handling and validation for VueJS forms

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published