Vuejs computed properties that depend on other, asynchronous, computed properties - javascript

In my Vuejs app I need to pass two computed properties to a component called avatar: an image surce and a string.
The problem is that not all items have a picture, and when they don't, vuejs throws an error because it cannot read property apples of undefined (because album_id is undefined).
The error is being thrown from that very long-winded src property in the avatar component, below:
<template>
<div class="apples">
<div id="mast" class="f3 b bb b--black-10">
<h2 class="ttc">Apple's List</h2>
</div>
<div id="content">
<li v-for="apple in apples" class="list-item">
<avatar :src="apple[ 'album_id '][ 'apples' ][ '256' ]" :size="50" :appletype="apple.type"></avatar>
</li>
</div>
</div>
</template>
<script>
import Avatar from './Avatar.vue';
import Apples from '#/api/apples';
export default {
name: 'apples',
asyncComputed: {
apples: {
async get() {
const apples = await Apples.getAll();
return apples.data;
},
default: []
}
},
components: {
Avatar
}
};
</script>
I need to somehow treat the data that I receive from the api before I use it in the html template, but I am unsure as to how to proceed. Creating a separate pictures array within the get() function just seems wrong.
Using v-if and v-else to check if there is a src property to pass down also seems very clumsy.
Do I create another, separate, computed method that iterates through my list of items and gets the picture src if the item has one?

I would suggest that you need to create getters for your apple sauce.. er.. source.. er.. src :)
<template>
<div class="apples">
<div id="mast" class="f3 b bb b--black-10">
<h2 class="ttc">Apple's List</h2>
</div>
<div id="content">
<li v-for="apple in apples" class="list-item">
<avatar :src="getAvatar(apple, 256)" :size="50" :appletype="apple.type"></avatar>
</li>
</div>
</div>
</template>
<script>
import Avatar from './Avatar.vue';
import Apples from '#/api/apples';
export default {
name: 'apples',
methods: {
getAvatar: function(obj, id) {
return obj.album_id.apples[ id ] | ''
}
},
asyncComputed: {
apples: {
async get() {
const apples = await Apples.getAll();
return apples.data;
},
default: []
}
},
components: {
Avatar
}
};
</script>
This allows you to create a graceful fallback with whatever arguments and implementation you choose.

Related

Redraw vue component on fetch update

When on button click I want to refresh list of items.
Button is trigger on a sibling component.
Watch method only gets called once. But I need a constant refresh
Parent element.
<template>
<div class="container">
<Filter #changedKeywords="reloadItems"></Filter>
<List :platforms="platforms" :filters="keywords"></List>
</div>
</template>
<script>
imports...
export default {
name: "Holder",
components: {Filter, List},
methods: {
reloadItems: function (data){
if(data.keywords) {this.keywords = data.keywords};
}
},
data(){
return {
keywords : null,
}
}
}
</script>
I want to redraw child this element multiple times, on each (filter)button click
<template>
<section class="list">
<div class="container">
<div class="holder">
<Game v-for="data in list" :key="data.id" :data="data" />
</div>
</div>
</section>
</template>
<script>
import Game from "./Game";
export default {
name: "List",
props: ['filters', 'platforms'],
components: {Game},
data() {
return{
list: [],
}
},
watch: {
filters: async function() {
console.log('gets called only once!!!'); // this is where I want to fetch new items
const res = await fetch('/api/game/list/9', {
method: 'POST',
body: JSON.stringify({'filters' : this.filters})
});
this.list = await res.json();
}
},
}
</script>
When you're watching objects and arrays you need to use a deep watcher.
The Solution
watch: {
filter: {
deep: true,
async handler(next, previous) {
//your code here
}
}
}
The Reason
Javascript primitives are stored by value, but Objects (including Arrays which are a special kind of Object) are stored by reference. Changing the contents of an Object doesn't change the reference, and the reference is what is being watched. Going from null to some object reference is an observable change, but subsequent changes aren't. When you use a deep watcher it will detect nested changes.

VueJs 2: How to display prop in another component?

I have a simple question, but cannot find how to solve.
There are 2 Vue 2 Components. In Component 1, two props are passed, which therefore are used in Component 2.
// Component 1
Vue.component('component1', {
props: {
titleTest: {
type: String,
required: true
},
textTest: {
type: String,
required: true
}
},
template: `
<div>
<div :title="titleTest">{{ titleTest }}</div>
<div :data-test="textTest">{{ textTest }}</div>
</div>
`,
created() {
console.log('Created1');
this.$root.$refs.component1 = this;
},
methods: {
foo: function() {
alert('this is component1.foo');
}
}
});
// Component 2
Vue.component('component2', {
template: `
<div>
<div>Some text</div>
<ul>
<li>List Item1</li>
<li>List Item2</li>
</ul>
<div>
<button id='test' type="submit" #click="bar">Text</button>
<component1 ref="component1" :title="test1" :data-test="test2"></component1>
</div>
</div>
`,
data: function() {
return {
test1: 'testText1',
test2: 'testText2'
};
},
methods: {
bar: function() {
this.$root.$refs.component1.foo();
}
},
created: function() {
console.log('Created2');
}
});
new Vue({
el: '#plotlyExample',
});
My idea, when I use Component 1 in Component 2 HTML template and bind data variables, they should be displayed. However, Vue sets only "title" and "data-test" but {{ titleTest }}, {{ textTest }} are not displayed. Additionally Vue sets props in one div, instead of two separate.
My ideal result is:
You named your props titleTest and textTest, that means you need to pass title-test and text-test, NOT title and data-test.
The reason they end up in your main <div> is because when Vue doesn't recognise them as props (because you used different names), it falls back on assuming they're regular HTML attributes (like class, id, and style)
In order for it to work, you either need to rename your props to title and dataTest in Component1, or you should use the title-test and text-test names in Component2.
You just need pass props to the component1
<component1 ref="component1" :title-test="test1" :text-test="test2"></component1>
you are missnaming the props in the component1 ( child component ) , you used title and data-test but your props names are titleTest and textTest ! so you should use title-test and text-test instead .
:prop-name="propValue"

How do I pass data to a component using Props in Vue2?

I have created a .Vue file to feature information on a cafe (Cafe Details Page). However, I would like to take parts of this details page and make it its own component, in order to manage any template updates more efficiently.
Therefore, I have created a Component (CafeHeader.vue) inside a components folder. I am trying to pass down the data from my array (Which is being used on my Cafe Details page) to this component using Props. However, I can't seem to get it to work.
The template for my Cafe Details Page is as below:
<template>
<div>
<div v-for="cafe in activeCafe">
<CafeHeader v-bind:cafes="cafes" />
<div class="content">
<p>{{ cafe.cafeDescription }}</p>
</div>
</div>
</div>
</template>
<script>
import CafeHeader from "./../../components/CafeHeader";
import cafes from "./../../data/cafes"; // THIS IS THE ARRAY
export default {
data() {
return {
cafes: cafes
};
},
components: {
CafeHeader,
},
computed: {
activeCafe: function() {
var activeCards = [];
var cafeTitle = 'Apollo Cafe';
this.cafes.forEach(function(cafe) {
if(cafe.cafeName == cafeTitle){
activeCards.push(cafe);
}
});
return activeCards;
}
}
};
</script>
Then, in a components folder I have a component called CafeHeader where I am wanting to use the data from the array which is previously imported to the Cafe Details page;
<template>
<div>
<div v-for="cafe in cafes">
<h1>Visit: {{cafe.cafeName}} </h1>
</div>
</div>
</template>
<script>
export default {
name: "test",
props: {
cafes: {
type: Array,
required: true
}
},
data() {
return {
isActive: false,
active: false
};
},
methods: {}
};
</script>
If in the CafeHeader component I have cafe in cafes, it does render data from the cafes.js However, it is every cafe in the list and I want just a single cafe.
<template>
<div>
<div v-for="cafe in cafes">
<h1>Visit: {{cafe.cafeName}} </h1>
</div>
</div>
</template>
The component also needed activeCafes on the v-for
<template>
<div>
<div v-for="cafe in activeCafes">
<h1>Visit: {{cafe.cafeName}} </h1>
</div>
</div>
</template>

Cannot set property 'products' of undefined in Vue.js [duplicate]

This question already has answers here:
Use arrow function in vue computed does not work
(6 answers)
Closed 2 years ago.
I am trying to import an array of objects from another JS file into a Vue component and let it render a card based on the object's properties. The import is working as I've logged it in the console and its returned the array, but when I try to assign it to an already existing array it throws TypeError: Cannot set property 'products' of undefined. Also, console.log(this.products) returns nothing but also doesn't throw an error. Below is my Products.vue component
<template>
<div>
<header>
<h1> Products </h1>
<br>
<h3> Browse All Our Products </h3>
</header>
<section>
<div v-for='item in products' v-bind:key='item.name' class="product">
<h3>{{ item.name }}</h3>
<div>
<img :src="item.img" />
</div>
<p>{{ item.desc }}</p>
<p>{{ item.modelNum }}</p>
</div>
</section>
</div>
</template>
<style scoped src='../assets/products.css'></style>
<script>
import { productList } from '../assets/db.js';
export default {
name: 'Products',
data: function() {
return {
products: [],
importList: productList,
};
},
created: () => {
this.products = productList;
//console.log(products);
}
}
</script>
don't use Arrow function, just change it like this:
created: function() {
this.products = productList;
//console.log(products);
}
Vue will bind this in data,method,mounted,computed...
should modify u code:
created: () => {
}
created () {
}

Vue JS - Problem with computed property not updating

I am quite new with VueJS and I have been having trouble lately with some computed properties which do not update as I would like. I've done quite some research on Stack Overflow, Vue documentation and other ressources but i haven't found any solution yet.
The "app" is basic. I've got a parent component (Laundry) which has 3 child components (LaundryMachine). The idea is to have for each machine a button which displays its availability and updates the latter when clicked on.
In order to store the availability of all machines, I have a data in the parent component (availabilities) which is an array of booleans. Each element corresponds to a machine's availability.
When I click on the button, I know the array availibities updates correctly thanks to the console.log. However, for each machine, the computed property "available" does not update is I would want it to and I have no clue why.
Here is the code
Parent component:
<div id="machines">
<laundry-machine
name="AA"
v-bind:machineNum="0"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="BB"
v-bind:machineNum="1"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
<laundry-machine
name="CC"
v-bind:machineNum="2"
v-bind:availableArray="this.availabilities"
v-on:change-avlb="editAvailabilities"
></laundry-machine>
</div>
</div>
</template>
<script>
import LaundryMachine from './LaundryMachine.vue';
export default {
name: 'Laundry',
components: {
'laundry-machine': LaundryMachine
},
data: function() {
return {
availabilities: [true, true, true]
};
},
methods: {
editAvailabilities(index) {
this.availabilities[index] = !this.availabilities[index];
console.log(this.availabilities);
}
}
};
</script>
Child component:
<template>
<div class="about">
<h2>{{ name }}</h2>
<img src="../assets/washing_machine.png" /><br />
<v-btn color="primary" v-on:click="changeAvailability">
{{ this.availability }}</v-btn>
</div>
</template>
<script>
export default {
name: 'LaundryMachine',
props: {
name: String,
machineNum: Number,
availableArray: Array
},
methods: {
changeAvailability: function(event) {
this.$emit('change-avlb', this.machineNum);
console.log(this.availableArray);
console.log('available' + this.available);
}
},
computed: {
available: function() {
return this.availableArray[this.machineNum];
},
availability: function() {
if (this.available) {
return 'disponible';
} else {
return 'indisponible';
}
}
}
};
</script>
Anyway, thanks in advance !
Your problem comes not from the computed properties in the children, rather from the editAvailabilities method in the parent.
The problem is this line in particular:
this.availabilities[index] = !this.availabilities[index];
As you can read here, Vue has problems tracking changes when you modify an array by index.
Instead, you should do:
this.$set(this.availabilities, index, !this.availabilities[index]);
To switch the value at that index and let Vue track that change.

Categories

Resources