5 common problems with Vue.js
After working with Vue.js for a while now I have noticed that everyone comes across the same pitfalls when starting out. That’s why I gathered the most common and hopefully, I can save you some time before hunting StackOverflow for an answer next time.
1. Why is “this” undefined?
Referencing this
in any of the lifecycle hooks (created, mounted, updated, etc.) it evaluates to undefined
mounted: () => {
console.log(this); // logs "undefined"
},computed: {
foo: () => {
console.log(this); // logs "undefined"
}
}
Why is this
evaluating to undefined
in these cases?
Both of those examples use an arrow function () => { }
, which binds this
to a context different from the Vue instance.
As per the documentation:
Don’t use arrow functions on an instance property or callback (e.g.
vm.$watch('a', newVal => this.myMethod())
). As arrow functions are bound to the parent context,this
will not be the Vue instance as you’d expect andthis.myMethod
will be undefined.
In order to get the correct reference to this
as the Vue instance, use a regular function:
mounted: function () {
console.log(this);
}
Alternatively, you can also use the ECMAScript 5 shorthand for a function:
mounted() {
console.log(this);
}
2. Make VueJS and jQuery play nice
The way to make Vue play nicely with other DOM-manipulating toolkits is to completely segregate them: if you are going to use jQuery to manipulate a DOM widget, you do not also use Vue on it (and vice-versa).
A wrapper component acts as a bridge, where Vue can interact with the component and the component can manipulate its internal DOM elements using jQuery (or whatever).
jQuery selectors outside of lifecycle hooks are a bad code smell. Your validatePhoneNumber
uses a selector and a DOM-manipulating call, but you are using Vue to handle keydown
events. You need to handle everything on this widget with jQuery. Don't use Vue to set its class or phone_number or handle its events. Those are all DOM manipulations. As I mentioned, if you wrap it in a component, you can pass props to the component and from those props, you can use jQuery to set class and phone_number.
3. Update parent data from the child component
From the documentation:
In Vue.js, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events. Let’s see how they work next.
Following is the code to pass props to a child element:
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
HTML:
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
JS:
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
4. Returning Promises from Vuex actions
Actions
in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction
returns a Promise
, makes an HTTP call and resolves or rejects the Promise
later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here...
// lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator
// and change something in state
resolve(response);
// Let the calling function know that http is done.
// You may send some data back
}, error => {
// http failed, let the calling function know
// that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction
, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created.
// Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
// Got some data,
// now lets show something in this component
}, error => {
// Got nothing from server.
// Prompt user to check internet connection
})
}
}
As you can see above, it is highly beneficial for actions
to return a Promise
. Otherwise, there is no way for the action initiator to know what is happening and when things are stable enough to show something on the user interface.
And a last note regarding mutators
- as you rightly pointed out, they are synchronous. They change stuff in the state
, and are usually called from actions
. There is no need to mix Promises
with mutators
, as the actions
handle that part.
5. Share data between different pages
You can pass the sharedData
as an URL param : 'window.location.href="pageB.html?sharedData=myData"' and access it in the other page. If you would have been using vue-router, you could just do
this.$route.params.sharedData
As you are not using it, You can just use plain javascript to get it, however, you will have to write a helper function like following to get the params, as explained here
var QueryString = function () {
// This function is anonymous, is executed immediately and
// the return value is assigned to QueryString!
var query_string = {};
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
// If first entry with this name
if (typeof query_string[pair[0]] === "undefined") {
query_string[pair[0]] = decodeURIComponent(pair[1]);
// If second entry with this name
} else if (typeof query_string[pair[0]] === "string") {
var arr = [ query_string[pair[0]],decodeURIComponent(pair[1]) ];
query_string[pair[0]] = arr;
// If third or later entry with this name
} else {
query_string[pair[0]].push(decodeURIComponent(pair[1]));
}
}
return query_string;
}();
Vue way
You can use centralised state management to manage your shared data, which will be available across page unless you reload the page.
You can define your state, like the following:
var store = {
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
this.state.message = newValue
},
clearMessageAction () {
this.state.message = 'action B triggered'
}
}
and you can use it in your components like the following:
var vmA = new Vue({
data: {
privateState: {},
sharedState: store.state
}
})
var vmB = new Vue({
data: {
privateState: {},
sharedState: store.state
}
})
A Better Vue way: Vuex way
However, Vue ecosystem has a dedicated redux
like state management system as well, called vuex which is quite popular among the community. You store all your projects/users etc in vuex state, and each component can read/update from the central state. If it's changed by one component, an updated version is available to all components reactively. You can initiate the data in one place using actions in vuex itself.
Following is the architecture diagram:
You can have a look at the official vuex documentation for examples on how to call API and save data in the store.
Source: Stackoverflow