Submit a form with Vue and Vuex

Mar 12, 2019

In this tutorial we'll go through async form submission using Vue.js and Vuex for state management

Today I'm releasing new functionality so that people from companies in the Bay Area can update their own company profiles. Up until this point I've had a list of 500+ Bay Area companies in an Airtable spreadsheet that I'd use to seed the Employbl database with company information. I'd add the company's basic information to my spreadsheet, along with tagging information about the industry they operate in and then reseed the companies table when I deployed the website.

This worked great, but I've received a ton of candidate feedback that more information about companies would be helpful for them in their job searching. I agree. It would be great to have companies list the technologies they use, how big the company is, their investors, a link to a recruiter or hiring manager's contact information and who knows what else! There are for sure sites out there for this, like Crunchbase and Stackshare but think there's unique value Employbl can deliver by mapping out these company locations.

In this tutorial we're going to go through the code so that employees from Bay Area companies can update (and add to) their own company profiles on Employbl. I'm going to keep this scoped to the frontend form with Vue.js and Vuex, though there were necessary updates to the PHP and MySQL side of things too to make this work! 🔧

Create your Vue component and register it to the Vue instance

I'm using Laravel for this project which comes with Vue support out of the box via Laravel Mix. You could create a new Vue frontend application using the Vue CLI tools. They are supported by the Vue.js core team. I don't recommend configuring webpack yourself since these tools are available to you!

With a Vue application setup, I need to register my new component and create a .vue file. When naming your files and registering your components use camel case (UpdateCompanyForm) and when rendering the component from HTML use dashed case (<update-company-form>).

Vue.component('UpdateCompanyForm', require('./path/to/your/component'));

The component will be rendered in my HTML by referencing the component name. I want this form to take in the company that needs to be updated, so I will pass that in as a Vue prop. The data will come from Blade and be passed into the prop as a string. We'll need to JSON.parse it to a Javascript object within the component itself.

<update-company-form company="{{ $company }}" ></update-company-form>

Build the Vue component

Then in my .vue file (compiled to HTML and Javascript with Webpack and vue-loader) I'll add in my HTML and Javascript that makes up the actual component.

<template>
  <div>
    <div class="row mb-3">
      <div class="col-md-5">
        <label>Address<span class="required">*</span></label>
        <input type="text" class="form-control" v-model="companyInfo.street" placeholder="123 Mulberry Lane Unit 407">
      </div>

      <div class="col-md-3">
        <label>City<span class="required">*</span></label>
        <input type="text" class="form-control" v-model="companyInfo.city" placeholder="San Francisco">
      </div>

      <div class="col-md-2">
        <label>State<span class="required">*</span></label>
        <select class="custom-select d-block w-100" v-model="companyInfo.state">
          <option v-for="(state,key) in stateAbbreviations" selected="state == companyInfo.state" :value="state" :key="key">
            {{state}}
          </option>
        </select>
      </div>

      <div class="col-md-2">
        <label>Zip<span class="required">*</span></label>
        <input type="text" class="form-control" v-model="companyInfo.zip">
      </div>
    </div>
    <div class="row">
      <div class="btn btn-success" @click="updateCompany">Submit</div>
    </div>
  </div>
</template>
<style>
  .required { color: red;}
</style>
<script>
  import _ from 'lodash';

  const stateAbbreviations = ["AK", "AL", "AR", "AZ", "CA", "CO", "CT", "DC",
    "DE", "FL", "GA", "HI", "IA", "ID", "IL", "IN", "KS", "KY", "LA",
    "MA", "MD", "ME", "MI", "MN", "MO", "MS", "MT", "NC", "ND", "NE",
    "NH", "NJ", "NM", "NV", "NY", "OH", "OK", "OR", "PA", "RI", "SC",
    "SD", "TN", "TX", "UT", "VA", "VT", "WA", "WI", "WV", "WY"];

  export default {
    props: {
      'company': {
        required: true,
        default() {
          return {};
        }
      },
    },
    data() {
      const company = JSON.parse(this.company);

      return {
        companyInfo: _.cloneDeep(company),
        stateAbbreviations,
      };
    },
    methods: {
      updateCompany() {
        // todo
      }
    }
  }
</script>

For the purposes of this tutorial I am only going to include code for updating a company's address information. The first thing you'll notice is that the input fields are bound using the v-model attribute. These map directly to fields that are defined in the object that the data function returns.

For the state select field we're taking advantage of Vue's built in v-for loop to go over all the states in our states array. The states array is accessible to the template because of the stateAbbreviations key-value pair in the object the data function returns. The selected field will match the existing state of the company.

We have one required prop to bring the company's data from the Laravel Blade HTML into the Vue component. It will be brought in as a string; we then parse and set a copy of comapny's info on our data object.

When the user clicks the submit button we will trigger a method to send a POST request to our server and update the company profile. In a normal Vue.js app this is where we would make the axios call. Because we're using Vuex we're going to handle this a bit differently.

Enter Vuex

Vuex is a state management library for Vue.js. It makes it easier and cleaner to update data across multiple components. Instead of making an API call in our local component and updating the state, we're going to dispatch an Event that will trigger an Action. If we need to update state across multiple components we could trigger a Mutation.

The first thing we're going to do, is from our component above trigger the action. When booting my app I instantiated my Vuex store and registered Vuex (Vue.use(Vuex)), making $store.dispatch available to the component.

In the component actions:

methods: {
  updateCompany() {
    this.$store.dispatch('updateCompanyInfo', this.companyInfo)
      .then((response) => {
        // show success message

      })
      .catch((err) => {
        // show error message
      });
  }
}

In our Vuex store, we have a property called actions. The event name maps directly to the action name.

actions: {
  async updateCompanyInfo({ commit }, companyInfo) {
    return axios.post('/my/url/endpoint', companyInfo)
      .then((response) => {
        commit('SOME_MUTATION', response.data)
      });
  },
}

We make our API call in the actions section. For the purposes of this form I don't have data to be updated in other parts of the application so committing a mutation isn't necessary. I've included some boilerplate if we did want to update the global store once the API call is complete. You cannot make API calls in Vuex Mutations.

If we did need a mutation, it would be defined under mutations property of the Vuex store and look something like this:

state: {
  someGlobalState: null,
},
mutations: {
  SET_MUTATION(state, responseData) {
    state.someGlobalState = responseData;
  },
},

someGlobalState would be null. Once the user clicked the button and triggered our action the global application state would update.

For instance, we could have another component like a navbar or sidebar that needs that new state information. To reference Vuex state in a Vue component use computed properties like so:

import {mapGetters, mapState} from 'vuex'

export default {
  created() {
    this.$store.dispatch('getCompanyInfo');
  },
  data() {
    return {

    }
  },
  computed: {
    ...mapState([
      'companyInfo'
    ]),
  }
}

This way, if any part of your application updates state information it will be reflected across all components.

Conclusion

This stuff is tricky and takes time to wrap your head around. It took time for me at least! When I built the first version of Employbl I was passing events around and using event listeners and event buses. It seemed to work fine but it got to be more complicated than it needed to be. What I hope you take away from this tutorial is that Vuex is very approachable and that you can use local state in conjunction with Vuex. Mutations and global state are only necessary if you have information that needs to be shared across multiple components.

For this update company form, all we need is to have a component trigger an action from the Vuex store. Sure we could have made the API call in our local component but I believe it's a good practice when using Vuex to keep your API calls grouped together in the Vuex store.

The code for Employbl is not open source, but I did build an open source Task App that uses Vue and Vuex that you can use as an example. (There's a backend too, written in Node.js with Hapi framework.)

Thanks for reading! If I wrote some bad code or if you have questions, comments or thoughts feel free to leave a (nice) comment! I am also on twitter @connor11528.