Data received in child as props is empty - vue.js

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().

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.

Vue.js Dynamically extend/replace child component method at runtime with access to parent scope

Is it possible to extend child component function at runtime in vue? I want to limit/stop child component function call based on parent scope logic (I want to avoid passing props in this specific case).
Overriding a component method is not a runtime solution/I can't have access to parent scope.
What I have tried and it does not working:
// Foo.vue
<template>
<button #click="func">Click me</button>
</template>
export default {
methods: {
func() {
console.log('some xhr')
}
}
}
// Bar.vue
<template>
<Foo ref="foo"/>
</template>
export default {
components: {Foo}
mounted() {
this.$nextTick(() => {
this.$refs.foo.func = function() {
console.log('some conditional logic')
this.$refs.foo.func()
}
})
}
}
For this usecase a better implementation would be defining the function in the parent itself and passing it through props. Since props are by default reactive you can easily control it from parent.
// Foo.vue
<template>
<button #click="clickFunction.handler">Click me</button>
</template>
export default {
name: 'Foo',
props: {
clickFunction: {
type: Object,
required: true
}
}
}
// Bar.vue
<template>
<Foo :clickFunction="propObject"/>
</template>
export default {
components: {Foo},
data() {
return {
propObject: {
handler: null;
}
};
}
mounted() {
this.$nextTick(() => {
if(some condition) {
this.propObject.handler = this.func();
} else this.propObject.handler = null;
})
},
methods: {
func() {
console.log('some xhr')
}
}
}
From what I managed to realize:
the solution in the code posted in the question really replaces the func() method in the child component. It's just that Vue has already attached the old method to the html element. Replacing it at the source will have no impact.
I was looking for a way to re-attach the eventListeners to html component. Re-rendering using an index key would not help because it will re-render the component with its original definition. You can hide the item in question for a split second, and when it appears you will receive an updated eventListener. However, this involves an intervention in the logic of the child component (which I avoid).
The solution is the $forceUpdate() method.
Thus, my code becomes the following:
// Foo.vue
<template>
<button #click="func">Click me</button>
</template>
export default {
methods: {
func() {
console.log('some xhr')
}
}
}
// Bar.vue
<template>
<Foo ref="foo"/>
</template>
export default {
components: {Foo}
mounted() {
this.$nextTick(() => {
let original = this.$refs.foo.func; // preserve original function
this.$refs.foo.func = function() {
console.log('some conditional logic')
original()
}
this.$refs.btn.$forceUpdate(); // will re-evaluate visual logic of child component
})
}
}

Data not updating from Event Bus

I'm trying to get a data from another component (A) to component (B).
Component A:
methods: {
setTemplate(template) {
bus.$emit("setEmailTemplate", template);
}
}
Here is where I want to fetch the data
Component B:
<template>
<div>
<p>{{ template }}</p>
</div>
</template>
<script>
import { bus } from "../app";
export default {
data() {
return {
template: ''
};
},
created: function() {
bus.$on("setEmailTemplate", (data) => {
this.template = data;
})
}
};
</script>
However when I run the code, template returns empty string. It seems that template is not being updated.
Your event name is different while emitting and listening.
It has to be same for event bus to work.
Change your methods in Component A like so :
methods: {
setTemplate(template) {
bus.$emit("setEmailTemplate", template);
}
}
AFTER EDIT
Without seeing more code, it would be hard to debug what problem you are facing.
I made a simple example of what you are trying. This might help you.
Working implementation attached.

Undefined variable - While api fetch | Axios | Props

My main component - Home
A really simple component, I pass the fetch variable to another component.
<template>
<Page actionBarHidden="true">
<ComponentA :api="api.somevariable"></ComponentA>
</Page>
</template>
<script>
import axios from "axios";
import ComponentA from "./ComponentA.vue";
export default {
data() {
return {
isLoading: false,
result: []
};
},
components: {
ComponentA,
},
created() {
this.loadData();
},
methods: {
async loadData() {
let self = this;
console.log("fetch");
self.isLoading = true;
const { data } = await Endpoints.get();
self.isLoading = false;
self.api = data;
console.log(data); // returns the data as intended
}
}
</script>
The componentA is also simple
<template>
<Label :text="somevariable"></Label>
</template>
<script>
export default {
data() {
return {
somevariable: 0
};
},
props: {
api: {
type: Number,
required: true
}
},
mounted() {
this.somevariable = this.api;
}
};
</script>
The error I am getting is [Vue warn]: Invalid prop: type check failed for prop "api". Expected Number with value NaN, got Undefined in the componentA, after some quoting and requoting of console.logs it actually picks up the value. I am not sure why is that, is my approach wrong? This frustrates me, can't figure it out for some hours already.
api isn't defined in the data for the first component, so it won't be reactive. That should be giving you a warning message in the console.
data () {
return {
api: null,
isLoading: false,
result: []
};
}
The second problem is that when the component first renders it won't yet have loaded api from the server. Using await won't help with this, rendering the template will happen before the asynchronous request has completed.
Given the way componentA is currently written it won't be able to cope with api being missing when it is first created. So you'll need to use a v-if to defer creation until that data is available:
<ComponentA v-if="api" :api="api.somevariable"></ComponentA>
Without the v-if check it'll just be passing the initial value of api, which in your original code is undefined. That is what caused the warning mentioned in the question.
When you talk about 'quoting and requoting of console.logs', I would assume that those changes are just triggering hot reloading, which could easily cause components to re-render with the new data. That wouldn't happen otherwise because of the lack of reactivity caused by api not being included in the original data.

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