Categories
Vue

Common Vue Problems — Bootstrap, Vuex Route Restrictions, and More

Vue.js makes developing front end apps easy. However, there are still chances that we’ll run into problems.

In this article, we’ll look at some common issues and see how to solve them.

Using Bootstrap in our App

If we only want to use the CSS from Bootstrap, then we can add the plain Bootstrap package and load the SASS files from it.

To load SASS files, we can use the sass-loader and node-sass .

To install them, we run:

npm install sass-loader node-sass --save-dev

Then we add the path to load the SASS files in the Webpack config file:

...
sassLoader: {
  includePaths: [
    path.resolve(projectRoot, 'node_modules/bootstrap/scss/'),
  ],
},
...

Now we can import the Bootstrap SASS file in our components:

<style lang="scss">
  @import "bootstrap";
</style>

If we want to use the JavaScript components from Bootstrap, then we should use BootstrapVue instead.

Using SASS Resource Loader

We can add the SASS preprocessor to our vue.config.js file by writing:

module.exports = {
  css: {
    loaderOptions: {
      sass: {
        data: `
          @import "@/scss/_variables.scss";
          @import "@/scss/_mixins.scss";
        `
      }
    }
  }
};

There’s no need for extra dependencies since they’re included with Vue CLI projects.

Call Method in Another Component in Vue

We can’t call another component’s method directly if we aren’t calling a method from a child component in the parent.

Instead, we’ve to pass events around by adding a global Vue instance that we can use to emit and listen to events.

For instance, we can write:

window.Event = new Vue();

to create a Vue instance.

Then we can call $emit on it by writing:

Event.$emit('createImage', item, response)

in one component.

Then in the mounted hook on another component then we can write:

mounted() {
  Event.$on('createImage', (item, response) => {
    // ...
  }
}

We listen to the createImage event and get the arguments that are passed into $emit in the callback parameters.

Adding Web Components in Vue Component

We can add web components in a Vue component directly.

For example, we can write:

<template id="app">
  <web-component>...</web-component>
</template>

Given that web-component is a web component, we can include it directly.

The Limits of v-html

v-html can only be used to render plain HTML code.

We can’t render Vue components with it.

For instance, if we have a component:

Vue.component('component', {
  template: '<span>component</span>',
})

Then we can’t have code like:

new Vue({
  el: '#app',
  data(){
    return {
      comp: '<component></component>'
    }
  }
})

...
<div id="app">
  <span v-html="comp"></span>
</div>

This will not work because component is a component.

All data bindings and Vue components are ignored.

Instead, we have to render plain HTML:

new Vue({
  el: '#app',
  data(){
    return {
      comp: '<p>foo</p>'
    }
  }
})
...
<div id="app">
  <span v-html="comp"></span>
</div>

That will be rendered with v-html since we have a p element.

Mount Multiple Vue Instances on Multiple Elements

If we want to attach multiple Vue instances on multiple elements, we can write:

const vues = document.querySelectorAll(".app");
[...vues].forEach((el, index) => new Vue({el, data: { message: `hello ${index}`}}))

We get all the elements with class app .

Then we use the spread operator on vues so we can use forEach on it,

In the callback, we can create new Vue instances.

Regex in Vue Router Paths

We can use regex in Vue Router paths.

For instance, we can write:

{ path: 'item/:id(d+)', name: 'itemCard', component: ItemCard }

Then we specified that the id parameter must be digits.

Custom Select Component that Binds to v-model

If we want to create custom select component that binds to v-model , then our component has to emit the input event and takes the value prop.

v-model is shorthand for both of those things together.

For instance:

<custom-select v-bind:value="val" v-on:input="val = $event.target.value"></custom-select>

can be rewritten as:

<custom-select v-model="val"></custom-select>

Then we can do anything in custom-event to emit the input event and take the value prop.

Conclusion

v-model is short for emitting the input event and taking the value property.

We can add Bootstrap SASS files with the SASS loader package.

Vue Router routes can have regex to restrict the values of parameters.

Categories
Vue

Solving Common Vue Problems — List Filtering, Global Libraries, Vuex, and More

Vue.js makes developing front end apps easy. However, there are still chances that we’ll run into problems.

In this article, we’ll look at some common issues and see how to solve them.

Filtering Lists in Vue Components

The proper way to filter lists is to use a computed property.

This is because computed properties return a value before v-for is run.

For instance, we can write:

computed: {
  filteredItems() {
    return this.items.filter(item => item.type.toLowerCase().includes(this.search.toLowerCase()))
  }
}

We put our filtering logic in the filterItems computed properties.

Then we can loop through the computed properties by writing:

<div v-for="item of filteredItems" >
  <p>{{item.name}}</p>
</div>

We just pass in the filteredItems computed property as a variable into our template.

Making HTTP Calls in Vue Components

We can make HTTP calls in Vue components with the Fetch API or a 3rd party HTTP client library like Axios.

For example, we can write:

methods: {
  async getData(){
    const res = await fetch('/some/url')
    const data = await res.json();
    this.data = data;
  }
}

fetch returns a promise so we can use async and await with it.

Remove the Hash from a URL

We can remove hashes from URLs generated by Vue Router by using history mode.

To enable it, we can write:

const router = new VueRouter({
  mode: 'history'
})

We set the mode to 'history' so that we can remove the hash.

Listening to Props Changes

We can listen to props changes by using the watchers.

To add a watcher for a prop, we can add a method to the watch property.

For instance, we can write:

{
  ...
  `props: ['myProp'],
  watch: {
    myProp(newVal, oldVal) {
      console.log(newVal, oldVal)
    }
  }
  ...
`}

We have the watch property and the myProp prop as indicated by the props property’s value.

We just make a method with the same name as the prop with a signature that takes the new and old values respectively.

Then we get the newVal value to get the new value, and the oldVal value to get the old value.

To start watching immediately when the prop value is set, we can add the immediate property and set it to true .

For example, we can write:

watch: {
  myProp: {
    immediate: true,
    handler (val, oldVal) {
      // ...
    }
  }
}

Communication Across Any Components

We can communicate between any components with the this.$dispatch method to dispatch an event that’s propagated to all components.

Then we can use this.$on to listen to the event that’s dispatched by this.$dispatch .

For example, we can write the following in one component:

export default {
  ...
  `created() {
    this.$dispatch('child-created', this)
  }
  ...
}`

Then in another component, we can write:

export default {
  ...
  `created() {
    this.$on('child-created', (child) => {
      console.log(child)
    })
  }
  ...
}`

We emit the event in the created hook so the event is emitted when the component is created but DOM content isn’t loaded yet.

In the 2nd component, we add the event listener in the created hook so that we can listen to events as soon as the component code is loaded.

child-created is the event name.

The child parameter has the event data in the 2nd argument of $dispatch .

Using Axios Globally in a Vue App

To make Axios available globally, we can set it as the property of Vue.prototype before we create the Vue instance.

This way, it’ll be returned with the Vue instance if we create the Vue instance.

For example, we can write:

import Axios from 'axios'

Vue.prototype.$http = Axios;

Then in our components, we can access it by using the this.$http variable.

For instance, if we want to make a get request, we can write:

this.$http.get('https://example.com')

Vuex Action vs Mutations

Vuex mutations are synchronous code that takes a name and a handler.

It’s used to update a state.

For instance, we can write:

import Vuex from 'vuex'

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment(state) {
      state.count++
    }
  }
})

Then state.count++ updates the count state when we dispatch the 'increment' action.

An action is like a mutation, but it can dispatch multiple mutations.

It can also be async.

For example, we can write:

incrementBy({ dispatch }, amount) {
  dispatch('INCREMENT', amount)
}

The dispatch function is a function to dispatch mutations.

Conclusion

There are differences between Vuex actions and mutations.

To filter items, we should do them in computed properties.

We can put things in Vue.prototype to make them available globally.

Categories
Vue

Common Vue Problems — State Updates, Vuex, and Event Handlers

Vue.js makes developing front end apps easy. However, there are still chances that we’ll run into problems.

In this article, we’ll look at some common issues and see how to solve them.

Passing event and argument to v-on in Vue.js

We can pass arguments to the method we call in the v-on directive in Vue.

For instance, we can write:

<input type="number" v-on:input="addToCart($event, item.id)" min="0" placeholder="0">

in our template.

Then in our component, we can write:

methods: {
  addToCart(event, id){
    console.log(id)
  }
}

$event has the object emitted from the input event.

id has the item.id that we passed in the template code.

The Purpose of nextTick

nextTick lets us do something after we changed the data.

It’s like setTimeout in plain JavaScript.

nextTick detects changes from data changes, so it’ll always run after a piece of data has been updated.

setTimeout isn’t aware of that.

For instance, if we have:

<template>
  <div>
    {{ val }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      val: 1
    }
  },
  mounted() {
    this.val = 2;

    this.$nextTick(() => {
      this.val = 3;
    });
  }
}
</script>

The initial value of this.val is 1.

Then the mounted hook is called and updates this.val is 2.

Once that update is done, then Vue updates this.val to 3.

Difference Between $mount() and $el

$mount lets us explicitly mount the Vue instance when we need to.

With it, we can delay the Vue instance until a particular element exists.

This is useful for adding Vue to legacy apps where the elements are added by manipulating the DOM.

el mounts the Vue instance straight into the DOM without checking whether it exists first.

Therefore, it’s useful for mounting the instance to a static element or something that we know always loads before the Vue instance.

[Vue warn]: Cannot find element

This error means that Vue can’t the element with the given select to mount the component on.

For instance, if we have:

const main = new Vue({
  el: '#main',
  data: {
    hello: 'home'
  }
});

Then the element with ID main isn’t found.

To fix that, we should make sure that the element is loaded before we create the Vue instance.

For example, we can write:

window.onload = () => {
  const main = new Vue({
    el: '#main',
    data: {
      hello: 'home'
    }
  });
}

or we can write:

window.addEventListener('load', () => {
  // add the same Vue instance as the previous example
})

They both let us mount the Vue instance after the given element is present in the DOM.

Load All Server-Side Data on Initial Vue.js Load

We can load the data into the Vuex store before creating the Vue instance by writing our own function to load it.

For example, we can write:

const store = (data) => {
  return new Vuex.Store({
    state: {
      exams: data,
    },
    actions,
    getters,
    mutations,
  });
}

fetch('/some/url')
  .then(response => response.json())
  .then((data) => {
    new Vue({
      el: '#app',
      router,
      store: store(data),
      template: '<App/>',
      components: { App },
    });
  });

We have a function to create the store with thestore .

Then we call the store function in our object that we passed into the Vue constructor.

The data we passed into the store is from the fetch function call.

This way, the data will be populated in the store before it goes to the store.

Import SASS or SCSS with a Vue CLI Project

We can import SASS or SCSS with a Vue app created with Vue CLI by adding the node-sass and sass-loaer packages.

Then we can load the SCSS files in our code.

To install the packages, we run:

npm install -D node-sass sass-loader

Then we can write:

import './styles/my-styles.scss'

to import the styles globally in main.js .

If we want to import a SASS or SCSS file in the file, then we write:

<style lang="scss">
  ...
</style>

to add make Vue aware of SCSS code.

Conclusion

To let us use SASS or SCSS code in an existing project, we can use the SASS loader package.

If we want to load data to a Vuex store before loading our Vue app, we can load the data, then create the Vuex store with the data.

Then we can create our Vue instance with the store.

We can pass anything to event handlers.

nextTick is handy for updating our state after some other update.

Categories
Vue

Adding Actions to a Vuex Store

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add actions to our Vuex store to change the store’s state.

What are Actions?

Actions are similar to mutations, but they commit mutations instead of mutating the state. Actions can also commit asynchronous operations unlike mutations.

For example, we can add a simple action as follows:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state) {
      state.count++;
    }
  },
  actions: {
    increase(context) {
      context.commit("increase");
    }
  }
});

An action takes a context object, which we can use to call commit to commit a mutation.

Dispatching Actions

We can dispatch actions by calling store.dispatch('increase') .

It’s much more useful than commit mutations directly because we can run asynchronous operations with it.

For example, we can dispatch an action as follows in Vue app:

index.js :

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state, payload) {
      state.count += payload.amount;
    }
  },
  actions: {
    increaseAsync({ commit }, payload) {
      setTimeout(() => {
        commit("increase", payload);
      }, 1000);
    }
  }
});

new Vue({
  el: "#app",
  store,
  methods: {
    ...Vuex.mapActions(["increaseAsync"])
  },
  computed: {
    ...Vuex.mapState(["count"])
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <button @click="increaseAsync({amount: 10})">Increase</button>
      <p>{{count}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have the action increaseAsync with the following code:

increaseAsync({ commit }, payload) {
  setTimeout(() => {
    commit("increase", payload);
  }, 1000);
}

In the action method, we commit the increase mutation with a payload object that we pass in when we dispatch the action.

In the Vue instance, we have:

methods: {
    ...Vuex.mapActions(["increaseAsync"])
},
computed: {
  ...Vuex.mapState(["count"])
}

which maps the actions in the store by calling mapActions , and added computed properties by mapping them from the store by using mapState so we get a count property which is computed from state.count as a computed property.

Then in the template, we call increaseAsync when we press the Increase button to update state.count after 1 second.

Finally, we should see the number updated since we mapped the state from the store.

Composing Actions

We can chain actions that return promises.

For example, we can create an action to update 2 counts as follows:

index.js :

const store = new Vuex.Store({
  state: {
    count1: 0,
    count2: 0
  },
  mutations: {
    increase(state, payload) {
      state.count1 += payload.amount;
    },
    decrease(state, payload) {
      state.count2 -= payload.amount;
    }
  },
  actions: {
    async increaseAsync({ commit }, payload) {
      return Promise.resolve(commit("increase", payload));
    },
    async decreaseAsync({ commit }, payload) {
      return Promise.resolve(commit("decrease", payload));
    },
    async updateCounts({ dispatch }, payload) {
      await dispatch("increaseAsync", payload);
      await dispatch("decreaseAsync", payload);
    }
  }
});

new Vue({
  el: "#app",
  store,
  methods: {
    ...Vuex.mapActions(["updateCounts"])
  },
  computed: {
    ...Vuex.mapState(["count1", "count2"])
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <button @click="updateCounts({amount: 10})">Update Counts</button>
      <p>Count 1: {{count1}}</p>
      <p>Count 2: {{count2}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have the updateCounts action switch calls dispatch with the increaseAsync action and payload . Then it calls dispatch with the decreaseAsync action and payload .

increaseAsync commits the increase mutation, and decreaseAsync commis the decrease mutation.

Since they all have Promise.resolve , they’re all async.

Then we include the updateCounts action from the store in our Vue instance with mapActions . And we also include the count1 and count2 states with mapState .

Then when we click the Update Counts button, we call updateCounts , and then count1 and count2 are updated as we click the button. count1 should increase by 10 each time and count2 should decrease by 10 each time we click it.

Conclusion

We can use actions to commit one or more mutations or dispatch other actions.

It’s handy for grouping store operations together and running asynchronous code since mutations are always synchronous.

We can use mapActions to include them in our components.

Actions are dispatched by calling dispatch , while mutations are committed with the commit method.

Categories
Vue

Adding Mutations to a Vuex Store

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add mutations to a Vuex store so that we can update the store’s state and propagate the change to components that uses the store.

Adding Mutations

We can add a mutation by putting in a function that changes the state by taking it from the parameter and then mutating it.

For example, we can write the following code:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state) {
      state.count++;
    }
  }
});

Then we can run the mutation function by running:

store.commit('increase')

Then state.count should go up by 1.

We can’t run mutation functions directly.

Commit with Payload

If we want to pass our own argument to it, we can add it after the state parameter, so we write:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increase(state, num) {
      state.count += num;
    }
  }
});

Then we can commit the mutation by writing:

store.commit('increase', 10)

Then state.count should go up by 10.

The second parameter is usually an object, so we can access property values by writing:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    `increase`(state, payload) {
      state.count += payload.amount;
    }
  }
});

Then we can commit the mutation by writing:

store.commit('increase', {
  amount: 10
})

Any state changes will be observed by components automatically if they access the store from within it.

Vue’s reactivity rules like initializing initial state and using Vue.set to update objects, etc. all applies to Vuex stores.

We can replace strings for mutation names with constants by assigning them to constants.

For example, we can change the example we have above to:

const INCREASE = "increase";

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    [INCREASE](state, payload) {
      state.count += payload.amount;
    }
  }
});

store.commit(INCREASE, { amount: 10 });

JavaScript since ES6 can have dynamic property names, so we can take advantage of that to replace names with constants.

Mutations Must Be Synchronous

Mutations must be synchronous because they’re untrackable since asynchronous code can be called at any time rather than line by line like synchronous code is.

Committing Mutations in Components

We can either use this.$store.commit to commit mutations or we can use Vuex’s helper methods to do so.

this.$store.commit

For example, we can use this.$store.commit as follows:

index.js :

const INCREASE = "increase";

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    [INCREASE](state, payload) {
      state.count += payload.amount;
    }
  }
});

new Vue({
  el: "#app",
  store,
  computed: Vuex.mapState(["count"]),
  methods: {
    increase(amount) {
      this.$store.commit(INCREASE, { amount });
    }
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <button @click"increase(10)">Increase</button>
      <p>{{count}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have the increase method as follows:

increase(amount) {
  this.$store.commit(INCREASE, { amount });
}

This is called by our template when we click the Increase button.

this.$store.commit(INCREASE, { amount }); will increase state.count by the amount we pass in, which is 10.

Then we get the state back by using mapState so we can display it in our template.

When we click Increase, we get 10, 20, 30, etc.

mapMutations

We can use mapMutations to map mutations to methods in our component as follows:

index.js :

const INCREASE = "increase";

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    [INCREASE](state, payload) {
      state.count += payload.amount;
    }
  }
});

new Vue({
  el: "#app",
  store,
  computed: Vuex.mapState(["count"]),
  methods: {
    ...Vuex.mapMutations([INCREASE])
  }
});

index.html :

<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vuex"></script>
  </head>
  <body>
    <div id="app">
      <button @click"increase({amount: 10})">Increase</button>
      <p>{{count}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, mapMutations maps the mutation methods to the component methods.

So:

...Vuex.mapMutations([INCREASE])

maps the increase method adds an increase method to the component which calls this.$store.commit(INCREASE) .

INCREASE is the string 'increase' that we have in the first line.

In the template, we just called increase({amount: 10}) when the Increase button is clicked to update state.count in the store.

Conclusion

We add mutations to our Vuex store to update the state of our store.

To do this, we add methods under the mutations object.

Then we can call this.$store.commit or use mapMutations to map mutation methods in the store to component methods.

Mutations must be synchronous since they need to be trackable.