Can not get the right data from Vue data - vuejs2

I have the following code in a component
data() {
return {
data: []
};
},
mounted() {
this.$axios.get("https://canteen.sjtu.edu.cn/CARD/Ajax/Place").then(res => {
this.data = res.data;
});
console.log(this.data);
},
but I can not get the updated data, for example, console.log(this.data) has no data I want, but I can get the right data in my template
<template>
<div>
<div>{{ data }}</div>
</div>
</template>
And if I console.log(this) in mounted, I can get the Vue component, and in it I can see the data I want!
Thanks a lot for your help !!

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.

Vue3 best way to fetch data based on asynchronous prop passed by parent

In Vue 3, my component "App" makes an asynchronous API request to retrieve info about a "purchase". This "purchase" is passed on to its child component "DeliveryInfo".
In "DeliveryInfo", I need to make another request based on the "customerId" property contained in the "purchase" prop. However, when "DeliveryInfo" receives the "purchase" prop, it's value is at first undefined. The second API request would then fail.
To avoid this, I used a watcher, so that when "DeliveryInfo" eventually gets the content of the "purchase" prop, it would then make a call to the API and update its own data.
I heard this is not good for performance. Could someone help me to improve my code ?
Here's my App.vue component :
<template>
<Purchase :purchase="purchase" />
</template>
<script>
import Purchase from "./components/Purchase"
export default {
name: "App",
components: { Purchase },
data() {
return {
purchase: []
};
},
methods: {
async fetchPurchase(id) {
const response = await fetch(`myapi.com/purchases/${id}`);
const data = await response.json();
return data;
}
},
async created() {
// example with a specific id
this.purchase = await this.fetchPurchase(79886);
}
}
</script>
And my DeliveryInfo component :
<template>
<div class="delivery-info embossed">
<div>
<h4>Adress</h4>
<p>{{ purchase.deliveryAdress }}</p>
<p>{{ purchase.deliveryCity }}</p>
</div>
<div>
<h4>Customer info</h4>
<p>{{ customerData.firstname }} {{customerData.lastname}}</p>
<p>{{ customerData.phone }}</p>
</div>
</div>
</template>
<script>
export default {
name: "DeliveryInfo",
props: ["purchase"],
data(){
return{
customerData: {}
}
},
methods:{
async fetchCustomer(){
if(!this.purchase){
return
}
const response = await fetch(`myapi/customers/${this.purchase.customerId}`)
this.customerData = await response.json()
}
},
watch : {
purchase(){
this.fetchCustomer()
}
}
}
</script>

Pass Vue js search filter functionality through single file components with EventBus

I have the following components:
/components/SearchBlogs.vue Search component to filter on blog.title and blog.description.
/components/BlogList.vue Here I list all the Blog items.
SearchBlogs.vue
<template>
<div>
<input type="text" v-model="search" #change="emitSearchValue" placeholder="search blog">
</div>
</template>
<script>
import { EventBus } from '../event-bus.js'
export default {
name: 'SearchBlogs',
data: () => {
return {
search: ''
}
},
methods: {
emitSearchValue() {
EventBus.$emit('search-value', 'this.search')
}
}
}
</script>
BlogList.vue
<template>
<div>
<div v-for="blog in filteredBlogs" :key="blog">
<BlogListItem :blog="blog" />
</div>
</div>
</template>
<script>
import BlogListItem from './BlogListItem'
import { EventBus } from '../event-bus.js'
export default {
name: 'BlogList',
components: {
BlogListItem,
},
data: () => {
return {
blogs: [],
searchvalue: ''
}
},
computed: {
filteredBlogs() {
return this.blogs.filter(blog =>
blog.name.toLowerCase().includes(
this.searchvalue.toLowerCase()
)
)
}
},
created() {
fetch('http://localhost:3000/blogs')
.then(response => {
return response.json();
})
.then(data => {
this.blogs = data;
}),
EventBus.$on('search-value', (search) => {
this.searchvalue = value;
})
}
}
</script>
In another page component Blogs I register both components:
<template>
<div>
<h1>Blog</h1>
<TheSidebar>
<SearchBlogs />
</TheSidebar>
<BlogList/>
</div>
</template>
Can anybody see what's missing here? I want, as soon as the user types something in the search input (from the SearchBlogs.vue component), it start filtering and updating the list.
Look at my solution condesandbox
Here is an explanation:
You don't need to use EventBus. You can communicate with Search Component by v-model, using prop value and emiting updated value from the Input.
Then your Main (List) Component is responsible for all the logic.
It keeps the state of a Search
It keeps the items and filtered Items
Thanks to that your Search Component is very clear and has no data, that means it has very little responsibility.
Please ask questions if I can add something to help you understand 😉
UPDATE:
EventBus is a great addition in some cases. Your case is simple enough, there is no need to add it. Right now your architecture is "over engineered".
When you have added listener on EventBus, on created:hookyou should always remove it while Component is being destroyed. Otherwise you can encounter a trouble with double calling function etc. This is very hard to debug, tryst me I'he been there 😉
Going with my suggestion gives you comfort of "no-need-to-remember-about-this" because Vue is doing it for you.
Hope that help.
Couple of issues but essentially the computed prop filteredData will look like:
computed: {
filteredData() {
return this.experiences.filter(
el => el.category.indexOf(this.search) > -1
);
}
}
Also, used quotes around 'this.search' when passing its value back which made it a string.
Fixed sandbox
https://codesandbox.io/s/reverent-lamarr-is8jz

VueJS Passing computed data as a prop returns undefined

I've tried and tried, but i can't figure it out the problem. From what I could read elsewhere, the variable passed to the child component gets sent as undefined before the data is available in the parent.
Please see here for reference:
the code in codesandbox
<template>
<div id="app">
<child :parentData="data.message"/>
</div>
</template>
<script>
import Child from "./components/Child";
export default {
name: "App",
components: {
Child
},
computed: {
quote() { return 'Better late than never' }
},
data() {
return {
data: { message: this.quote } ,
thisWorks: { message: "You can see this message if you replace what is passed to the child" }
};
}
};
</script>
Then in the child:
<template>
<div>
<h1>I am the Child Component</h1>
<h2> {{ parentData }}</h2>
</div>
</template>
<script>
export default {
name: "Child",
props: {
parentData: { type: String, default: "I don't have parent data" },
},
};
</script>
The answer is, you cannot access the value of this.quote because at the moment the data objectis creating, the computed object actually does not exist.
This is an alternative, we will use the created() lifecycle hook to update the value of data object:
created(){
this.data = {
message: this.quote
}
},
You don't need to change any things, just adding those line of codes is enough.
I've already tested those codes in your CodeSandbox project and it works like a charm.
Hopefully it helps!

How do I make reactive global property in vuejs plugin?

I put $testCounter in a plugin to make it global :
Vue.use({
install(Vue) {
Vue.prototype.$testCounter = 0;
Vue.prototype.$incrementCounter = () => {
Vue.prototype.$testCounter++;
};
});
I want to output it in some component. I also need its value to be updated globally, and reactively :
<template>
<p>{{ $testCounter }}</p>
</template>
<script>
mounted() {
let comp = this;
comp.watcherId = setInterval(() => {
comp.$incrementCounter();
// I want to remove this line and still be reactive :
comp.$forceUpdate();
}, 1000);
}
</script>
I need the property to be reactive, I tried a multiple solution as watchers, computed props, vm.$set(...), but I can't find the right way to do this.
Solution 1: use Vuex
Solution 2: set the global reactive data in root component and use it by calling this.$root
demo: https://jsfiddle.net/jacobgoh101/mdt4673d/2/
HTML
<div id="app">
<test1>
{{$root.testCounter}}
</test1>
</div>
Javascript
Vue.component('test1', {
template: `
<div>
test1
<slot></slot>
</div>
`,
mounted() {
setInterval(() => {
this.$root.incrementCounter();
}, 1000)
}
});
new Vue({
el: "#app",
data: {
testCounter: 1
},
methods: {
incrementCounter: function() {
this.testCounter++;
}
}
})