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.

Categories
Vue

Adding Getters 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 getters to a Vuex store to add states derived from other store states.

Adding Getters to Our Store

We can add a getter to our Vuex store by adding a getters property as follows:

index.js :

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: "todo1", done: true },
      { id: 2, text: "todo2", done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done);
    }
  }
});

new Vue({
  el: "#app",
  store,
  computed: {
    doneTodos() {
      return this.$store.getters.doneTodos;
    },
    ...Vuex.mapState({
      todos: "todos"
    })
  }
});

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">
      <div>
        <p>Todos:</p>
        <div v-for="todo of todos">{{todo.text}}</div>
        <p>Done Todos:</p>
        <div v-for="todo of doneTodos">{{todo.text}}</div>
      </div>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code, we have a getters object:

getters: {
  doneTodos: state => {
    return state.todos.filter(todo => todo.done);
  }
}

that derives the state from the todos state.

Then we accessed doneTodos getter by writing:

computed: {
  doneTodos() {
    return this.$store.getters.doneTodos;
  },
  ...Vuex.mapState({
    todos: "todos"
  })
}

in the Vue instance.

Then the items are rendered in our template and we get:

Todos:

todo1
todo2
Done Todos:

todo1

displayed.

Method-Style Access

We can return a function in a getter function so that we can call the getters from a component with one or more arguments.

For instance, if we want to make a getter that gets a todo by ID, we can write the following:

index.js :

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: "todo1", done: true },
      { id: 2, text: "todo2", done: false }
    ]
  },
  getters: {
    getTodoById: state => id => {
      return state.todos.find(todo => todo.id === id);
    }
  }
});

new Vue({
  el: "#app",
  store,
  computed: Vuex.mapState({
    todos: "todos"
  }),
  methods: {
    getTodoById(id) {
      return this.$store.getters.getTodoById(id);
    }
  }
});

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">
      {{getTodoById(1).text}}
    </div>
    <script src="index.js"></script>
  </body>
</html>

Then we see todo1 displayed on the screen.

We have the following code to define our getter:

getters: {
  getTodoById: state => id => {
    return state.todos.find(todo => todo.id === id);
  }
}

The code above returns a function and then returns a todo with the given ID.

Then we access it in our Vue instance by defining the getTodoById method as follows:

getTodoById(id) {
  return this.$store.getters.getTodoById(id);
}

Finally, we render it in our template by writing:

{{getTodoById(1).text}}

The mapGetters Helper

We can use the mapGetters helper to map our getters to computed properties as follows:

index.js :

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: "todo1", done: true },
      { id: 2, text: "todo2", done: false }
    ]
  },
  getters: {
    doneTodosCount: state => {
      return state.todos.filter(todo => todo.done).length;
    }
  }
});

new Vue({
  el: "#app",
  store,
  computed: Vuex.mapGetters(["doneTodosCount"])
});

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">
      {{doneTodosCount}}
    </div>
    <script src="index.js"></script>
  </body>
</html>

Then we get 1 displayed since we mapped doneTodosCount getter to a computed property with the same name with:

computed: Vuex.mapGetters(["doneTodosCount"])

Conclusion

We can use getters to add states that are derived from another state.

To define a getter, we add a method to the getters property that returns something derived from the state or a function that derives something from a state.

To include it in our components, we can use the this.$store.getters method or use the mapGetters method.