How To run a function in Vuejs after the component in created? - vue.js

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

Related

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 do I access programmatically created refs in vue.js?

I would like to access refs in a vue.js component, where the ref itself is created dynamically like so:
<style>
</style>
<template>
<div>
<lmap class="map" v-for="m in [1, 2, 3]" :ref="'map' + m"></lmap>
</div>
</template>
<script>
module.exports = {
components: {
lmap: httpVueLoader('components/base/map.vue'),
},
mounted: function(){
console.log('all refs', this.$refs);
// prints an object with 3 keys: map1, map2, map3
console.log('all ref keys', Object.keys(this.$refs));
// would expect ["map1", "map2", "map3"], prints an empty array instead
Vue.nextTick().then(() => {
console.log('map1', this.$refs["map1"]);
// would expect a DOM element, instead prints undefined
})
},
destroyed: function(){
},
methods: {
},
}
</script>
However this seems not to work (see above in the comments), and I can't figure why.
I think the problem is that you are importing the component asynchronously, with httpVueLoader, which then downloads and imports the component only when the component is rendered from the dom, therefore, the component has not yet been imported into the nextTick callback.
I suggest you put a loaded event in the map.vue component, maybe in mounted lifecycle , which will be listened to in the father, example #loaded = "showRefs"
surely when the showRefs(){ } method is invoked, you will have your refs populated ;)
Try using a template string e.g
`map${m}`
You have to wait until components have been rendered / updated. This works:
module.exports = {
data: function () {
return {
};
},
components: {
lmap: httpVueLoader('components/base/map.vue'),
},
mounted: function(){
},
destroyed: function(){
},
updated: function(){
Vue.nextTick().then(() => {
console.log('all ref keys', Object.keys(this.$refs));
console.log('map1', this.$refs['map1'][0].$el);
})
},
methods: {
},
}

Vue component computed not reacting

I have 2 components OperatorsList and OperatorButton.
The OperatorsList contains of course my buttons and I simply want, when I click one button, to update some data :
I emit select with the operator.id
This event is captured by OperatorList component, who calls setSelectedOperator in the store
First problem here, in Vue tools, I can see the store updated in real time on Vuex tab, but on the Components tab, the operator computed object is not updated until I click antoher node in the tree : I don't know if it's a display issue in Vue tools or a real data update issue.
However, when it's done, I have another computed property on Vue root element called selectedOperator that should return... the selected operator : its value stays always null, I can't figure out why.
Finally, on the button, I have a v-bind:class that should update when the operator.selected property is true : it never does, even though I can see the property set to true.
I just start using Vue, I'm pretty sure I do something wrong, but what ?
I got the same problems before I used Vuex, using props.
Here is my OperatorList code :
<template>
<div>
<div class="conthdr">Operator</div>
<div>
<operator-button v-for="operator in operators" :op="operator.id"
:key="operator.id" #select="selectOp"></operator-button>
</div>
</div>
</template>
<script>
import OperatorButton from './OperatorButton';
export default {
name: 'operators-list',
components : {
'operator-button': OperatorButton
},
computed : {
operators() { return this.$store.getters.operators },
selected() {
this.operators.forEach(op =>{
if (op.selected) return op;
});
return null;
},
},
methods : {
selectOp(arg) {
this.$store.commit('setSelectedOperator', arg);
}
},
}
</script>
OperatorButton code is
<template>
<span>
<button type="button" v-bind:class="{ sel: operator.selected }"
#click="$emit('select', {'id':operator.id})">
{{ operateur.name }}
</button>
</span>
</template>
<script>
export default {
name: 'operator-button',
props : ['op'],
computed : {
operator() {
return this.$store.getters.operateurById(this.op);
}
},
}
</script>
<style scoped>
.sel{
background-color : yellow;
}
</style>
and finally my app.js look like that :
window.Vue = require('vue');
import Vuex from 'vuex';
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
const store = new Vuex.Store({
state: {
periods : [],
},
mutations: {
setInitialData (state, payload) {
state.periods = payload;
},
setSelectedOperator(state, payload) {
this.getters.operateurs.forEach( op => {
op.selected = (op.id==payload.id)
})
},
},
getters : {
operators : (state) => {
if (Array.isArray(state.periods))
{
let ops = state.periods
.map( item => {
return item.operators
}).flat();
ops.forEach(op => {
// op.selected=false; //replaced after Radu Diță answer by next line :
if (ops.selected === undefined) op.selected=false;
})
return ops;
}
},
operatorById : (state, getters) => (id) => {
return getters.operators.find(operator => operator.id==id);
},
}
});
import Chrono from './components/Chrono.vue';
var app = new Vue({
el: '#app',
store,
components : { Chrono },
mounted () {
this.$store.commit('setInitialData',
JSON.parse(this.$el.attributes.initialdata.value));
},
computed: {
...mapState(['periods']),
...mapGetters(['operators', 'operatorById']),
selectedOperator(){
this.$store.getters.operators.forEach(op =>{
if (op.selected) return op;
});
return null;
}
},
});
Your getter in vuex for operators is always setting selected to false.
operators : (state) => {
if (Array.isArray(state.periods))
{
let ops = state.periods
.map( item => {
return item.operators
}).flat();
ops.forEach(op => {
op.selected=false;
})
return ops;
}
}
I'm guessing you do this for initialisation, but that's a bad place to put it, as you'll never get a selected operator from that getter. Just move it to the proper mutations. setInitialData seems like the right place.
Finally I found where my problems came from :
The $el.attributes.initialdata.value came from an API and the operator objects it contained didn't have a selected property, so I added it after data was set and it was not reactive.
I just added this property on server side before converting to JSON and sending to Vue, removed the code pointed by Radu Diță since it was now useless, and it works.

Vue model not updating

When I try to update my custom text-area component's model data this.message='<span id="foo">bar</span> the text and html does not display in the htmltextarea tag like it should, but I can see the update applied in the Vue dev tool's console. I've also tried switching to an object instead of a string and using Vue.set, but this does not work either.
Any suggestions on how to fix this?
The goal with the htmlTextArea component is to get the users text from the htmlTextArea tag (this works), manipulate this text and bind it back to the textarea, but with HTML in it.
Custom text-area component:
<template>
<div contenteditable="true" #input="updateHTML" class="textareaRoot"></div>
</template>
<script>
export default {
// Custom textarea
name: 'htmlTextArea',
props:['value'],
mounted: function () {
this.$el.innerHTML = this.value;
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML);
}
}
}
</script>
Other component:
<template>
...
<htmlTextArea id="textarea" v-model="message"></htmlTextArea>
...
</template>
<script>
data: {
return {
message: 'something'//this works
}
}
...
methods: {
changeText() {
this.message='<span id="foo">bar</span>'//this does not
}
},
components: {
htmlTextArea
}
</script>
You need to set the value explicitly after the value props change. you can watch for value change.
<template>
<div contenteditable="true" #input="updateHTML" class="textareaRoot"></div>
</template>
<script>
export default {
// Custom textarea
name: "htmlTextArea",
props: ["value"],
mounted: function() {
this.$el.innerHTML = this.value;
},
watch: {
value(v) {
this.$el.innerHTML = v;
}
},
methods: {
updateHTML: function(e) {
this.$emit("input", e.target.innerHTML);
}
}
};
</script>
Change the data property into a function, as you have it defined it is not reactive.
data () {
return {
message: 'something'//this works
}
}
Now when you update the message property in your method, the component will update accordingly.
Reactivity in depth

When are `data` evaluated?

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