VueJS computed property using boolean prop not updating - javascript

I have a Vue (2.6.11 via Nuxt) component that gets a Boolean property from its parent and uses it to calculate additional computed properties. After the initial rendering all values are shown as expected. However, when a parent flips the value of the passed-down property, only some values change in this component. Specifically DIVs bound directly to the property and original are both fine, but flipped and stringed never change again.
Assigning the original property to a local var before any evaluation within the computed property function makes no difference in the outcome.
Changing computed properties to methods doesn't solve the issue either. It is still just the first two that update properly.
Note that the code below is stripped to a bare minimum to demonstrate the issue.
<template>
<div class="x">
<div class="y">
<div class="x">
<div>{{ flag }}</div>
</div>
<div class="x">
<div>{{ original }}</div>
</div>
<div class="x">
<div>{{ flipped }}</div>
</div>
<div class="x">
<div>{{ stringed }}</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "FlagBox",
props: {
"flag": {
type: Boolean
}
},
computed: {
original: function() {
return this.flag;
},
flipped: function() {
return !this.flag;
},
stringed: function() {
return this.flag ? "yes" : "no";
}
}
}
</script>
What am I missing? Thanks.

I was not able to reproduce the issue but my best guess is that the radio button you are using is emitting a string value (e.g., "false" instead of false) which is causing it to be true even when it's not.
Please check your props using the vue devtools to verify the data type being passed.
There is no other possible explanation for this, the code seems fine.

Related

Cannot dynamically pass a prop to a component within a v-for loop in Vue js

I have an array sheets initialised in data, has an item pushed to it when a button is pressed
data() {
return {
sheets: []
};
}
And in the html I'm trying to add a Card component for each element in this array and pass the data as a prop, but none of the components get rendered and there is no error message. I also tried putting the v-for directly on the component but it has the same result
<div id="sheets">
<template v-for="c in sheets">
<Card :info="c"/>
</template>
</div>
Meanwhile if I do something like this, it displays all the data in the array correctly, so I dont understand whats going wrong here
<div id="sheets">
<template v-for="c in sheets">
<span>{{c}}</span>
</template>
</div>
--
Solution
In my component the data from prop was being manipulated in the created() function, it works after I did this in the mounted() function instead
Make sure you have following things done correctly:
Imported your Card component
Passing and accessing the props
There are no conflicts in the variable names and id names
Next thing you need to know and is a must while using v-for is adding :key to the element which acts as unique id and lets Vue detect that each element is unique inside the v-for. One thing to be noted while using :key is that, you cannot use :key on the <template> tag.
Adding a validation using v-if would be a good idea, so v-for only executes if the array is not empty.
<div id="sheets">
<template v-if="sheets.length">
<Card v-for="(sheet,index) in sheets" :key="index" :info="sheet" />
</template>
</div>
Edit 1: As mentioned by Michal in the comments section that using index can lead to difficulties while debugging issues. You can use either :key="sheet" or if sheet is object including some unique id in it, then use :key="sheet.id" and get rid of the index
// use this if sheet is not an object
<div id="sheets">
<template v-if="sheets.length">
<Card v-for="sheet in sheets" :key="sheet" :info="sheet" />
</template>
</div>
OR
// use this if sheet is an object having some unique id in it
<div id="sheets">
<template v-if="sheets.length">
<Card v-for="sheet in sheets" :key="sheet.id" :info="sheet" />
</template>
</div>
As :key is not mandatory, It should work without that as well. I just created a working fiddle below. Please have a look and try to find out the root cause.
Vue.component('card', {
props: ['info'],
template: '<span>{{ info }}</span>',
});
var app = new Vue({
el: '#app',
data: {
sheets: [],
count: 0
},
methods: {
create() {
this.count++;
this.sheets.push('Sheet' + this.count);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="create">Create a Card!</button>
<template v-for="c in sheets">
<Card :info="c"></Card>
</template>
</div>

Vue 3 Composition API, Props, and v-if rendering despite false value

I have an issue I don't think I'm really understanding here. I have a child component included which passes an "active" prop in and can be set to true or false. The idea is if it's passed "true" then a part of the component shows, and if it's passed "false" it doesn't.
from what I understand, I should be able to just use the prop name and do something like:
<template>
<div v-if="active">this is the value of active: {{active}}</div>
</template>
The issue here is if I directly set v-if in the above statement to true or false, then it works as expected. If I pass it in as a prop, it always shows regardless of whether true or false.
Works (doesn't show anything):
<template>
<div v-if="false">this is the value of active: {{active}}</div>
</template>
Doesn't Work (contents in the div shows regardless of value of active):
//-File1---------------------------------------
<template>
<myComponent active=false />
</template>
//-File2---------------------------------------
<template>
<div v-if="active">this is the value of active: {{active}}</div>
</template>
<script>
export default{
props:['active']
}
</script>
Why is this? I confirmed, by displaying the value of "active" that it's passing in as false, but it's still rendering despite the value being false. Am I missing something here? I've tried playing with quotes, without quotes, passing it into a local value using ref and using that:
import { ref } from 'vue';
export default{
props:['active']
setup(props,ctx){
const active = ref(props.active);
return {
active
}
}
}
that also did not work.
It's because your prop is passed in as string from parent component (the default behavior like all other html properties). In order to pass in the prop as boolean, you need to use v-bind syntax or : for short so that false is evaluated as a javascript expression instead of string:
<template>
<myComponent v-bind:active="false" />
</template>
Or
<template>
<myComponent :active="false" />
</template>
on your export default,
props: {
active: {
type: Boolean,
default: false
}
}
on your component template,
<template>
<div v-if="active !== false"> show only when active {{ active }}</div>
</template>
when using the component, bind the active element to false
<myComponent :active="false" />

Vue template isn't rendering in for loop

So after following a beginner Vue tutorial to setup a Todo app, I decided to try to adapt some parts of it for a website I'm trying to make. What I'm stuck on is that despite everything saying my for-loop is supposed to work, it doesn't.
The project itself was created using the vue-cli, and most of the code copy-pasted from the tutorial. (which is working fine with its own for-loop)
It seems like the data might be not passed onto the template maybe?
I have tried:
having the info inside the props and data sections
passing whole object and only parameters to the template
tried with hard-coded values inside array which is iterated on
(After setting up a new vue-cli project:)
App.vue:
<template>
<div id="app">
<create-section v-on:create-section="addSection" />
<section v-for="section in sections" v-bind:key="section.title" :info="section"></section>
</div>
</template>
<script>
import CreateSection from "./components/CreateSection";
import Section from "./components/Section";
export default {
name: "App",
components: {
CreateSection,
Section
},
data() {
return {
sections: []
};
},
methods: {
addSection(section) {
this.sections.push({
title: section.title,
description: section.description
});
console.log(
"Added to sections! : " + section.title + " | " + section.description
);
console.log("Sections length: " + this.sections.length);
}
}
};
</script>
Section.vue
<template>
<div class="ui centered card">
<div class="content">
<div class="header">{{ info.title }}</div>
<div>{{ info.description }}</div>
</div>
</div>
</template>
<script type = "text/javascript" >
export default {
props: {info: Object},
data() {
return {};
}
};
</script>
Expected result:
Display Section template on the website (after creating it with addSection that another script calls. Not included for brevity)
Actual result:
Nothing is displayed, only a empty tag is added
I believe the problem is that you've called it Section. As <section> is a standard HTML element you can't use it as a component name.
There is a warning built into the library but it seems to be case sensitive, which isn't entirely helpful. Try changing your components section to this:
components: {
CreateSection,
section: Section
},
You should then see the warning.
The fix would just be to call it something else.
This is mentioned in the first entry in the style guide:
https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential
section is an existing HTML5 element, you should name your section component something different.
If you really want to name the component Section, register it as 'v-section'
The problem is that when you do the loop in the <section v-for="section in sections" v-bind:key="section.title" :info="section"></section> the Array sections is not ready, there is nothing there.. so when you add new things to this array you need to trigger (computed prop) to send again the data to the section component.
Aside from the issue with using an existing HTML5 command as a name for your Vue component (you should change that to another name by the way), you should also look into how you declared the props within Section.vue. The code below shows the correct way to do it:
<script type = "text/javascript" >
export default {
props: ['info'],
data() {
return {};
}
};
</script>
The props take in the name of the property being declared from the parent component and should be a string.
Hope this helps.

Vue.js mounted function not accessing component properties

I'm not very new to Vue.js which is probably why I feel like I've been running mad all morning :). While creating a component, which I usually do, quite frequently, in this case, I had to initialize Google Maps within the mounted function, which seems like the right place to do that. In the mounted function, I would access the id property of a nested input field and attach an event listener to it. Pretty simple right?
Well, I figured that when I try to use the component multiple times on my page, I'm somehow accessing the same (seemingly shared) this variable within the mounted function.
Not sure why exactly this happens and/or if it's a feature but to make it even weirder, the props yield correct values within the template. (and within the methods as well)
Component Definition
<template>
<div class="LocationInput">
<input
type="text"
:id="id"
/>
</div>
</template>
<script>
export default {
name: 'LocationInput',
props: ['id'],
mounted() {
console.log('Component object representation ==> ', this)
console.log('ID ==> ', this.id)
}
}
</script>
Using my component...
<template>
<div class="MyTravelApp">
<LocationInput id="id1"/>
<LocationInput id="id2"/>
</div>
</template>
<script>
import LocationInput from './components/LocationInput';
export default {
components: { LocationInput }
}
</script>
What I get at the end of the day is the correct id values in the template but in my console, the exact same object and id are logged as you can see below. Notice how the _uid property is the same thing for both.
To make matters even worse, after modifying the this variable in the mounted function, while inspecting, I observed that the second component has that property modified as well. So they are essentially sharing the same object, which is extremely weird.
I would like to know if anyone has had similar issues and how to deal with it.
No self-closing tags for components.
Vue templates need to be valid HTML. There are no "self closing tags"
in HTML5, it's an XHTML syntax which is now outdated and you should
never use it.
(Later note:)
FYI self-closing tags works in 2.0 as long as you don't use in-dom
templates.
You may also be having an issue with camelCase vs. kebab-case. The snippet below behaves as expected.
Vue.component('locationInput', {
template: '#location-input-template',
props: ['id'],
mounted() {
console.log('Component object representation ==> ', this._uid)
console.log('ID ==> ', this.id)
}
});
new Vue({
el: '#my-travel-app'
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<template id="location-input-template">
<div class="LocationInput">
<input type="text" :id="id">
</div>
</template>
<div id="my-travel-app">
<location-input id="id1"></location-input>
<location-input id="id2"></location-input>
</div>

Javascript vuejs component in for loop

I have a v-for loop in vuejs that displays a component on each iteration. This is an autocomplete component that searches and displays product names when a user types in the input box.
I have a #change="setProduct" attribute on each component that correctly calls my setProduct method in my parent component.
But how can I know which of component was updated? All thats passed to the setProduct method is the details of the product that was emitted, but I don't know which component emitted the event to know which line to update.
Here is some relevant code:
This is in the parent component
<template>
<div class="row" v-for="line, i in invoice.InvoiceLines">
<div class="col-xs-5">
<auto-complete :list="productList" :value="line.Product.name" #change="setProduct"></auto-complete>
</div>
...
</div>
</template>
<script>
export default {
data() {
return {
invoice:{},
productList:[]
},
}
methods:{
setProduct(product){
//product has the details of the new product that was selected. But I don't know which invoice line it is referring to.
},
}
}
</script>
The component responds to a user clicking a selection in a dropdown, and then issues $emit('change', product);
The component has no knowledge of the parent component, so it doesn't know which invoice line it refers to. I could pass the index into the child component and then pass it back out, but that seems anti-pattern for vue.
Maybe there is an easier way for me to go about this?
Thanks for your help.
Since you're using v-for, so you can actually retrieve the index of the items in invoice.InvoiceLines and you can pass whatever your want into setProduct:
<template>
<div class="row" v-for="(line, i) in invoice.InvoiceLines">
<div class="col-xs-5">
<auto-complete
:list="productList"
:value="line.Product.name"
#change="setProduct(line, i, $event)"></auto-complete>
</div>
...
</div>
</template>
Then in JavaScript:
methods: {
setProduct(product, index, event){
// product - the 'line' responsible in invoice.InvoiceLines
// index - the index of line in invoice.InvoiceLines
// event - the event object
},
}

Categories

Resources