Introduction
In this article, we will talk about side effects in VueJS, and how to prevent some of them. Side effects are not outrightly bad since they might have some usecase like in a setTimeout()
function. But we need to try to ensure that we know about these side effects.
What are side effects?
A side effect is when a function changes or modifies data that does not exist in its local scope. A simple example is a function that receives a parameter which is an Array, and inside that function, it changes the data in that array.
Example:
const numberList = [ 1, 2, 3, 4, 5, 6 ];
function addItem( item ) {
return item.push( ( item.length - 1 ) + 1 );
}
addItem( numberList )
console.log( numberList )
From this example above, we find out that numberList is been mutated when additem is called.
Side Effects in Vue
Example 1: Side Effects in Computed Properties
Lets try to create a side effect example in a computed property.
<template>
<div>
Unsorted Data: {{ languages }}
</div>
<div>
Sorted Data: {{ sortLanguages }}
</div>
</template>
<script>
export default {
name: ‘languages-list’,
data() {
return {
languages: [
{
name: "React",
},
{
name: "Vue",
},
{
name: "Angular",
},
{
name: "Svelte",
}
]
},
},
computed: {
sortLanguages() {
return this.languages.sort( ( a, b ) => {
if ( a.name < b.name ) { return -1; }
if ( a.name > b.name ) { return 1; }
return 0;
} )
}
}
}
</script>
From the example above, we would see that the Unsorted Data and Sorted Data are the same. this.languages data model gets mutated when sortedLanguages is referenced. This is a side effect that can result in unexpected behaviours in your application if not properly handled.
Example 2: Side effects wWen Accessing Getters
Lets try to create a side effect example when accessing and assigning a getters data model.
// store/index.js
state: {
languages: [
{
name: "React",
},
{
name: "Vue",
},
{
name: "Angular",
},
{
name: "Svelte",
}
]
},
getters: {
getLanguages: function ( state ) {
return state.languages;
}
},
actions: {
sortLanguages: function ( context ) {
const languages = JSON.parse( JSON.stringify( context.getters.getLanguages ) );
return languages.sort( ( a, b ) => {
if ( a.name < b.name ) { return -1; }
if ( a.name > b.name ) { return 1; }
return 0;
} )
}
}
From this example, we would see that the store gets updated when we call (dispatch) the action sortLanguages. This is a side effect that can result in unexpected behaviours in your application, because the languages state is been mutated.
How Do We Resolve These Side Effects?
- Shallow clone
computed: {
sortLanguages() {
const languagesClone = this.languages.slice();
return languagesClone.sort( ( a, b ) => {
if ( a.name < b.name ) { return -1; }
if ( a.name > b.name ) { return 1; }
return 0;
} )
}
}
When we shallow clone a language's array using the .slice() method, it returns a shallow copy of a portion of the array into a new array object. We do this to prevent a mutation of the language's data model.
- Deep clone
computed: {
sortLanguages() {
const languagesClone = json.parse( json.stringify( this.languages ) );
return languagesClone.sort( ( a, b ) => {
if ( a.name < b.name ) { return -1; }
if ( a.name > b.name ) { return 1; }
return 0;
} )
}
}
To deep clone, we can simply use json.parse(json.stringify(())
on our data model.
JSON.stringify converts the data model to a string which removes references from the data model. JSON.parse converts the string back to an array of objects.
In Vue, side effects that modify reactive properties in the store can be hidden and cause unexpected bugs which can be difficult to trace and debug. It's my hope that this article helps you better identify and respond to side effects in VueJS.
If you have any questions or run into any trouble, feel free to reach out on Twitter or Github.