When are `data` evaluated? - vuejs2

From the lifecycle diagram I am not able to determine when are data evaluated.
See the example code below:
<template>
...
</template>
<script>
function generateUniqUserRef () {
return Math.random() + new Date().valueOf()
}
export default {
data() {
return {
user_ref: generateUniqUserRef()
}
}
}
</script>
Shall user_ref get calculated only once OR should it re-evaluate again on re-render?
I'm just about to try it. I just would like to have a formal explanation possibly with a reference.

beforeCreate()- called after the vue instance has been initialized by
new Vue({}). Here the data is not observed i.e the vue instance
does not know what is initialized inside data option.
created()-called after the vue instance is created. Here the vue insance know what reactive properties are inside data option and you can set up (change) any property inside data option
Shall user_ref get calculated only once OR should it re-evaluate again on re-render?
No it will get calculated only once. Re-render takes place when there is change in data and causes virtual dom to be re-rendered , so only the operations that are dependents on the dom take place again.
But it is better you calculate the user_ref in the created() as it will be called only once.
<script>
export default {
data() {
return {
user_ref: null
}
},
created(){
this.user_ref = Math.random() + new Date().valueOf();
}
}
</script>
You can run this code on you machine check the console logs
<template>
<div>
<h1>{{ msg }}</h1>
</div>
</template>
<script>
alert("hi i just ran"); // will only run once , not on every re-render
export default {
name: 'hello',
data () {
return {
msg: 'initial message'
};
},
beforeCreate(){
console.log('from before create', this.msg); // undefined
console.log('from before create', this.msg === 'initial message'); // false
},
created(){
console.log('from created', this.msg); // 'initial message'
console.log('from created', this.msg === 'initial message'); //true
},
mounted(){
// changes the msg
setTimeout(()=>{
this.msg = 'initial message changed';
}, 1000); // causes dom to re-render
},
beforeUpdate(){
console.log('from before update', this.msg) // 'initial message changed'
setTimeout(()=>{
this.msg = 'initial message changed again from before update';
}, 1000);
},
updated(){
console.log('from updated', this.msg)
}
}
</script>
<style scoped>
</style>
Source: options/lifecyclehooks

Related

Vue child component not rerending after parent component's data value updates

I am trying to have a child component update its props that were passed from the parents at the start of the rendering. Since the value is coming from a fetch call, it takes a bit of time to get the value, so I understand that the child component will receive a 'null' variable. But once the fetch call is completed, the value is updated but the child component still has the original null value.
During my search for a solution, I found that another way was to use Vuex Stores, so I implemented it with the count variable and had a button to call a commit and later dispatch with an action function to the store to increment it's value but when the increment happens, it doesn't show the new value on the screen even though with console logs I confirmed it did change the value when the function was called.
I guess I don't fully understand how to update the value of a variable without reassigning it within it's own component or having to call a separate function manually right after I change the value of a data variable.
App.vue
<template>
<div id="app">
<div id="banner">
<div>Title</div>
</div>
<p>count: {{count}}</p> // a small test i was doing to figure out how to update data values
<button #click="update">Click </button>
<div id="content" class="container">
<CustomDropdown title="Title Test" :valueProps="values" /> // passing the data into child component
</div>
</div>
</template>
<script>
import CustomDropdown from './components/CustomDropdown.vue'
export default {
name: 'App',
components: {
CustomDropdown,
},
data() {
return {
values: null
count: this.$store.state.count
}
},
methods: {
update() {
this.$store.dispatch('increment')
}
},
async created() {
const response = await fetch("http://localhost:3000/getIds", {
method: 'GET',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
});
const data = await response.json();
this.values = data // This is when I expect the child component to rerender and show the new data. data is an array of objects
console.log("data", data, this.values) // the console log shows both variables have data
}
}
</script>
CustomDropDown.vue
<template>
<div id="dropdown-container" class="">
<b-dropdown class="outline danger" variant="outline-dark" :text="title" :disabled="disabled">
<b-dropdown-item
v-for="value in values"
:key="value.DIV_ID"
href="#">
{{value.name}}
</b-dropdown-item>
</b-dropdown>
</div>
</template>
<script>
export default {
name: 'CustomDropdown',
components: {},
props: {
title: String,
valuesProp: Array,
disabled: Boolean
},
data() {
return {
values: this.valuesProp
}
},
methods: {
},
created() {
console.log("dropdown created")
console.log(this.valuesProp) //Always undefined
}
}
</script>
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state() {
return {
count: 0,
divisionIds: []
}
},
mutations: {
increment (state) {
console.log("count", state.count)
state.count++
}
},
actions: {
increment (state) {
console.log("count action", state.count)
state.commit('increment')
}
}
})
data in your child component CustomDropdown.vue is not reactive: therefore the value of this.values is not updated when the prop changes. If you want to alias a prop, use computed instead:
export default {
name: 'CustomDropdown',
components: {},
props: {
title: String,
valuesProp: Array,
disabled: Boolean
},
computed: {
values() {
return this.valuesProp;
}
},
created() {
console.log("dropdown created");
}
}
If you want to console log the most updated values of this.valuesProp, you will need to watch it: the same if you want for this.values.
One thing you can do is to use a v-if in your child component to only render it after you get your result from you api.
It would be something like:
<CustomDropdown title="Title Test" :valueProps="values" v-if="values"/>
This way you would make sure that your child component gets rendered only when values are available.
It would only be a bad solution if this api call took so long and you needed to display the child component data to the user before that.
Hey you can simply watch it your child component
watch: { valuesProp: function(newVal, oldVal) { // watch it if(newVal.length > 0) do something }
it will watch for the value changes and when you get your desired value you can perform whatever hope it will help you you dont need store or conditional binding for it.

Passing data from a Child Component to the Parents Pop in VueJs

I have a button component which calls an API, and I want to push the returned response up to the parent, where it will become the 'translatedText' prop, however, I believe I'm using the $emit incorrectly, due to the error: `Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '$emit'). How do I best capture the response data and pass it to my parent prop, and is using $emit the best use in this instance?
TranslationButton.vue
<template>
<b-button type="is-primary" #click="loadTranslations()">Übersetzen</b-button>
</template>
<script>
export default {
name: "TranslationButton",
props: {
translatedText: ''
},
methods: {
loadTranslations() {
fetch('http://localhost:3000/ccenter/cc_apis')
.then(function(response) {
return response.text();
})
.then(function(data) {
console.log(data);
this.$emit('translatedText', this.data);
console.log(data)
})
},
},
};
</script>
Parent Component Props:
props: {
data: Array,
translatedText: '',
showAttachments: {
type: Boolean,
default: false,
}
},
How Child Component is called in Parent Component:
<translation-button #translatedText="loadTranslations()" />
Best practise when passing data from child to parent is emitting events.
this.$root.$emit('translatedText', this.data);
than
this.$root.$on('translatedText', () => { // do stuff })
by emits you pass value to parent component,
#translatedText="loadTranslations()" - its event listner, fireing on your child comp emit
do #translatedText="loadTranslations" instead of #translatedText="loadTranslations()"
and add this loadTranslations as a method to parent comp
BTW
if you dont use arrow funcs, and you use this.data it's pointing to object passed to .then, it will be undefined i guess...
The problem is with the usage of this. It does no longer point to your component inside the promise then() method.
You should create a new variable and initialize it with the value of this and use that variable to emit the event.
E.g.
loadTranslations() {
const _this = this;
fetch().then(response => _this.$emit(response));
}
if you want to pass data from child to parent, you need to use $emit like the below code
child:
<template>
<b-button type="is-primary" #click="loadTranslations">Übersetzen</b-button>
</template>
<script>
export default {
name: "TranslationButton",
props: {
TranslatedText: ''
},
methods: {
loadTranslations() {
const self= this; // change added
fetch('http://localhost:3000/ccenter/cc_apis')
.then(function(response) {
return response.text();
})
.then(function(data) {
console.log(data);
self.$emit('changeTitle', data) // change added
})
}
}
</script>
parent:
<template>
<translation-button #changeTitle="ChangeT" />
</template>
......
methods:{
ChangeT(title)
{
console.log(title)
},
}

Data received in child as props is empty

I'm making call to json from parent and sending the resulted data to child as props.
But in child data is coming as zero.
parent.vue
<template>
<subscriberGraph :propSubData="subData" /> // child component
</template>
<script>
data() {
return {
subData: [] // subData declaration
};
},
methods: {
async getSubscribers() {
this.subData = await d3.json("./data/subscribers.json"); // calling json
console.log("1: ", this.subData); // variable is declared in this file and data is getting displayed here,
}
},
mounted() {
this.getSubscribers();
}
</script>
Below is child component.
subscriberGraph.vue
<script>
export default {
mounted() {
console.log("2: ", this.propSubData); // data length: 0
}
props: ["propSubData"]
</script>
EDIT: subData is already declared, that's what i've mentioned in the comment beside console.log("1"...). anyway updating the code above pls check.
You have to initialise the subData in the data section to use it. Since it will take time to get response from the request, you can use v-if condition while the child component is calling. This will prevent rendering the child component before the response is set.
<template>
<subscriberGraph :propSubData="subData" v-if="subData && subData.length>0" /> // child component
</template>
<script>
export default {
data:()=>{
return {
subData:null
}
},
methods: {
async getSubscribers() {
this.subData = await d3.json("./data/subscribers.json"); // calling json
console.log("1: ", this.subData); // variable is declared in this file and data is getting displayed here,
}
},
mounted() {
this.getSubscribers();
}
}
adding a watch{} solved the issue.
subscriberGraph.vue
<script>
export default {
methods: {
drawBarGraph(data) {
console.log("3: ", data); // working now !!
}
},
mounted() {
this.drawBarGraph(this.propSubData);
},
props: ["propSubData"],
watch: {
propSubData: function(newData) {
this.drawBarGraph(newData);
}
}
</script>
you need to watch changes, initially you won't get data because child comp has not yet received data when mounted. Later, when data is received, watch{} observes and updates data, hence data is now available in drawBarGraph().

How To run a function in Vuejs after the component in created?

I have created a component which has a function which makes external API calls and then fills an array. I used created() life hook to run the function for the 1st time. I am passing a variable from the parent component into this component and then based upon this variable change I want the function to run again.
How do I achieve this.
Attaching my code below
<template>
<div>
<p>{{ data_to_show_on_mainpage }}</p>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'GetCategoryItemsAndDisplayOne',
props: ['categoriesfordisplay','ismainpage', 'catalogselected'],
data(){
return {
IsMainPage_1 : "",
data_to_show_on_mainpage : [],
}
},
watch: {
catalogselected: function(){
this.GetItemsToShowonMainPage()
}
},
methods:{
changevalue(){
console.log("i am reducing it to emplty after change of catalog")
this.IsMainPage_1 = this.catalogselected
this.data_to_show_on_mainpage = []
},
CatlogService(catlog_name,category,gender,mainpage){
let url = "http://localhost:5000/xyz/" + (this.catalogselected).replace(/'/g,"%27") +"/api/"+ (gender) + "/catalogvis/" + (category) +"/items"
console.log(encodeURI(url))
axios.get(encodeURI(url)).then((resp)=>{
this.data_to_show_on_mainpage.push(resp.data.response.Results.results[0])
})
.catch(err => {
console.log("we got an error the url is " + url)
console.log(err);
})
},
GetItemsToShowonMainPage(){
this.changevalue()
if(this.categoriesfordisplay.men_for_display.length>0){
for(let i =0;i<this.categoriesfordisplay.men_for_display.length;i++){
let category = this.categoriesfordisplay.men_for_display[i].replace(/"/g,"%27");
this.CatlogService(this.catalogselected,category,'men',this.ismainpage)
}
}
if(this.categoriesfordisplay.women_for_display.length>0){
for(let i = 0;i<this.categoriesfordisplay.women_for_display.length;i++){
let category = this.categoriesfordisplay.women_for_display[i].replace(/"/g,"");
this.CatlogService(this.catalogselected,category,'women',this.ismainpage)
}
}
},
},
created(){
this.GetItemsToShowonMainPage()
}
}
</script>
<style>
</style>
How Do i trigger the GetItemsToShowonMainPage() function whenever the catalogselected varaible is changed.
It looks fine.
As #a-lau says, make sure the parent is updating the catalogselected prop
Btw, you can write your watcher this way and remove completely the created hook:
watch: {
catalogselected: {
handler: "GetItemsToShowonMainPage",
immediate: true
}
}
If you still have issues you might want to write a minimal reproduction on https://codesandbox.io/s/vue

Vue.js - Keep Alive Component - Error next Tick

Description
I'm trying to take advantage of the keep-alive functionality of vue-js 2.3 so my AJAX call is made only once.
Problem
The second time I try to open the popup component I get this error :
Error in nextTick: "TypeError: Cannot read property 'insert' of undefined"
TypeError: Cannot read property 'insert' of undefined
Steps
Click on the button to display the popup
Wait for one second
Close the popup
Click again on the button
https://jsfiddle.net/4fwphqhv/
Minimal reproduction example
<div id="app">
<button #click="showDialog = true">Show Component PopUp</button>
<keep-alive>
<popup v-if="showDialog" :show-dialog.sync="showDialog"></popup>
</keep-alive>
</div>
<template id="popup">
<el-dialog :visible.sync="show" #visible-change="updateShowDialog">{{asyncData}}</el-dialog>
</template>
Vue.component('popup', {
template: '#popup',
props : ['showDialog'],
data(){
return {
show: this.showDialog,
asyncData: "Loading please wait"
}
},
methods: {
updateShowDialog(isVisible) {
if (isVisible) return false;
this.$emit('update:showDialog', false )
}
},
created:function (){
const _this = this
setTimeout(() => _this.asyncData = 'Async Data was loaded' , 1000)
},
});
var vm = new Vue({
el: '#app',
data: {
showDialog: false,
},
});
Real code of the popup component
<template>
<el-dialog title="Order in progress" size="large" :visible.sync="show" #visible-change="updateShowLoadOrder"></el-dialog>
</template>
<script>
let popUpData;
export default {
name: '',
data () {
return {
ordersInProgress: [],
show: this.showLoadOrder
}
},
props: ['showLoadOrder'],
methods: {
updateShowLoadOrder (isVisible) {
if (isVisible) return false;
this.$emit('update:showLoadOrder', false)
}
},
created () {
const _this = this;
if (!popUpData) {
axios.get('api/mtm/apiGetOrdersInProgress').then((response) => {
_this.ordersInProgress = popUpData = response.data;
});
} else {
this.ordersInProgress = popUpData;
}
}
}
</script>
Ok. So your problem here is the wrong life-cycle hook.
If you change created to activated... it should work. It did for me in your JS fiddle.
activated:function (){
setTimeout(() => this.asyncData = 'Async Data was loaded' , 1000)
}
There are two other hooks, activated and deactivated. These are for keep-alive components, a topic that is outside the scope of this article. Suffice it to say that they allow you to detect when a component that is wrapped in a tag is toggled on or off. You might use them to fetch data for your component or handle state changes, effectively behaving as created and beforeDestroy without the need to do a full component rebuild.
SOURCE: here