Why can't I assign properties directly into my data? - javascript

I can't access my question props to assign its property directly into my data properties. Using the property directly from my template works, but can't assign the property into the data.
I can only access the value of props from my template:
props: ['question'],
data () {
return {
}
};
computed: {
upVote () {
return this.question.upvoted
},
count () {
return this.question.vote_count
}
}
methods: {
upVoteIt () {
if(User.loggedIn()) {
this.upVote = !this.upVote
this.upVote ? this.incr() : this.decr()
}
},
incr () {
this.count ++
},
decr () {
this.count --
}
}
<v-icon size="50" :color="upcolor" #click="upVoteIt">fas fa-sort-up</v-icon>
<span> {{ count }} </span>
I get the value if I use the props directly inside my template instead of reassigning into the data property.

You can use .sync modifier. It is a special feature that you can use when you want synchronize props with your parent component : Here is the documentation . you will no longer use the data.
So how it works, you need in your child compoment emit an event update question with the updated value :
props: ['question'],
methods: {
upVoteIt () {
if(User.loggedIn()) {
this.$emit('update:question', {
...this.question,
upvoted: !this.question.upvoted,
count: this.question.upvoted ? this.question.count - 1 : this.question.count + 1
}
}
}
}
<v-icon size="50" :color="upcolor"
#click="upVoteIt">fas fa-sort-up</v-icon>
<span> {{ question.vote_count }} </span>
Now in your parent you need to do something like this :
<YourQuestionComponent
:question.sync="myQuestionVariable"
/>

Related

Changes to properties in mounted not triggering computed in VueJS

I have a VueJS component which contains a button whose class and text are computed properties and changes every time the button is clicked. They are changing fine as long as I click on the button once it is loaded. I wanted to store the state in localStorage and if I reload the page set the text and class based on the value stored. The value of ordered is changing but the button text and class are not reflecting that in UI. Does anyone have any suggestion as to what I may be doing wrong? Following is the source
<template>
<div class="main-view">
<button type="button" :class="order_button_style" #click="on_order_button_click()">
{{ order_button_text }}
</button>
</div>
</template>
<script>
export default {
name: "FoodComponent",
props: {
item: Object
},
methods: {
on_order_button_click() {
this.item.ordered = !this.item.ordered;
localStorage.setItem(this.item.id, this.item.ordered);
}
},
mounted() {
var storedState = localStorage.getItem(this.item.id);
if (storedState) {
this.item.ordered = storedState;
}
},
computed: {
order_button_text() {
return this.item.ordered === true ? "Ordered" : "Order";
},
order_button_style() {
return this.item.ordered === true
? "button ordered-button"
: "button unordered-button";
}
}
};
</script>
What you will get from the local storage is a string. In mounted, ordered property will be a string instead of a boolean so you order_button_text computed property condition will never be true. To fix this you can just convert storedState property to a boolean :
mounted() {
const storedState = localStorage.getItem(this.item.id) === 'true';
if (storedState) {
this.item.ordered = storedState;
}
},

Computed property was assigned to but it has no setter - a toggle component

I am creating a simple switch toggle component in Vue where it has a v-model and #updated. But I can't seem to change the model when the user toggles the switch. First I was getting the error to avoid mutating a prop directly. But now I am getting another error.
[Vue warn]: Computed property "isSwitchOn" was assigned to but it has
no setter.
The component is meant to be used like this
<iswitch v-model="switchGender" #updated="handleUpdatedGender" />
Here is the component itself
export default {
template: `
<span
#click="toggleSwitch"
:class="{ active: isSwitchOn }">
<span class="toggle-knob"></span>
</span>
`,
props: ['value'],
methods:
{
toggleSwitch()
{
this.isSwitchOn = !this.isSwitchOn
this.$emit('input', this.isSwitchOn)
this.$emit('updated')
}
},
computed:
{
isSwitchOn()
{
return this.value
}
},
};
The error is triggered by this statement: this.isSwitchOn = !this.isSwitchOn. You are trying to assign a value to a computed property but you didn't provide a setter.
You need to define your computed property as follow for it to work as a getter and a setter:
computed:
{
isSwitchOn:
{
get()
{
return this.value
},
set(value)
{
this.value = value
}
}
}
Also, it is not advised to mutate a prop directly. What you could do is to add a new data property and sync it with the value prop using a watcher.
I think something like this will work:
props: ['value'],
data()
{
return {
val: null
}
},
computed:
{
isSwitchOn:
{
get()
{
return this.val
},
set(value)
{
this.val = value
}
}
},
watch: {
value(newVal) {
this.val = newVal
}
}
Computed properties are by default getter-only, but you can also provide a setter when you need it. Check official documentation
computed:
{
isSwitchOn() {
get() { return this.value }
set(val) { this.value = val }
}
}
Alternative way:
In your parent component:
<iswitch ref="switcher" #input="methodForInput" v-model="switchGender" #updated="handleUpdatedGender" />
methods: {
methodForInput(event){
this.$refs.switcher.isSwitchOn = event;
}
}
In your child component:
export default {
template: `
<span
#click="toggleSwitch"
:class="{ active: isSwitchOn }">
<span class="toggle-knob"></span>
</span>
`,
data() {
return {
isSwitchOn: false
};
},
methods:
{
toggleSwitch()
{
this.isSwitchOn = !this.isSwitchOn
this.$emit('input', this.isSwitchOn)
this.$emit('updated')
}
}
};
Updates 3: Sorry, didn't include parent component at first.

Vue.js Component rendering after prop update

In Vue.js i have a component (Answer Component) like this:
<template>
<a class="quiz-input-choice" :class="{'quiz-input-choice--selected': answer.selected}"
#click="toggleSelect()" :selected="answer.selected">
<img :src="answer.image_path"/>
<p class="quiz-input-choice__description">{{answer.title}}</p>
</a>
</template>
<script>
export default {
props: ['answer'],
methods: {
toggleSelect() {
this.$parent.$emit('answer-selected', this.answer.id);
}
}
}
</script>
If in the parent (Question Component) I update the "selected" attribute of the element, this component will not be rerendered.
export default {
props: ['question'],
components: {QuizAnswer},
created: function () {
let _self = this;
this.$on('answer-selected', id => {
let i = _self.question.answers.map(item => item.id).indexOf(id);
let answer = _self.question.answers[i];
answer.selected = !answer.selected;
});
}
}
In Vue Developer Console, i checked that Answer component data are updated, so the answer is marked as selected. Anyway, is not rendered with the "quiz-input-choice--selected" class.
If, strangely, I update from the parent other attribute of the prop (for example (answer.title), then the child component is rendered correctly with also the class "quiz-input-choice--selected".
So i guess it's a problem of detecting changes from the child.
Thank you everybody for the answers.
I discovered the problem. The "selected" attribute of the answer was not present in the initial object, so Vue cannot make reactive that attribute.
https://v2.vuejs.org/v2/guide/reactivity.html
I solved making reactive that property in the parent component.
created() {
let self = this;
this.question.answers.forEach(function (answer) {
self.$set(answer, 'selected', false);
});
},
I think you have a structural issue here. You shouldn't submit an event to your parent, since the component is supposed to be self-contained.
What you can do however is emitting an event in the child component (Answer) that will be catch in the parent (Question).
Answer.vue
<template>
<a class="quiz-input-choice" :class="{'quiz-input-choice--selected': answer.selected}"
#click="toggleSelect()" :selected="answer.selected">
<img :src="answer.image_path"/>
<p class="quiz-input-choice__description">{{answer.title}}</p>
</a>
</template>
<script>
export default {
props: ['answer'],
methods: {
toggleSelect() {
this.$emit('answer-selected');
}
}
}
</script>
Question.vue
Your template will have be catching the event like this (I don't know where your answers are so I assume that you have a answers array) :
<answer
v-for="(answer, index) in answers"
:answer="answer"
#answer-selected="answerSelected(index)"
></answer>
And your script will look like this :
export default {
props: ['question'],
components: {QuizAnswer},
data() {
return {
answers: [],
selectedAnswer: -1,
};
},
watch: {
selectedAnswer(newIndex, oldIndex) {
if (oldIndex > -1 && this.answers.length > oldIndex) {
// Reset old value
this.answers[oldIndex].selected = false;
}
if (newIndex > -1 && this.answers.length > newIndex) {
// Set new value
this.answers[newIndex].selected = true;
}
},
},
methods: {
answerSelected(index) {
this.selectedAnswer = index;
},
},
};

Passing props dynamically to dynamic component in VueJS

I've a dynamic view:
<div id="myview">
<div :is="currentComponent"></div>
</div>
with an associated Vue instance:
new Vue ({
data: function () {
return {
currentComponent: 'myComponent',
}
},
}).$mount('#myview');
This allows me to change my component dynamically.
In my case, I have three different components: myComponent, myComponent1, and myComponent2. And I switch between them like this:
Vue.component('myComponent', {
template: "<button #click=\"$parent.currentComponent = 'myComponent1'\"></button>"
}
Now, I'd like to pass props to myComponent1.
How can I pass these props when I change the component type to myComponent1?
To pass props dynamically, you can add the v-bind directive to your dynamic component and pass an object containing your prop names and values:
So your dynamic component would look like this:
<component :is="currentComponent" v-bind="currentProperties"></component>
And in your Vue instance, currentProperties can change based on the current component:
data: function () {
return {
currentComponent: 'myComponent',
}
},
computed: {
currentProperties: function() {
if (this.currentComponent === 'myComponent') {
return { foo: 'bar' }
}
}
}
So now, when the currentComponent is myComponent, it will have a foo property equal to 'bar'. And when it isn't, no properties will be passed.
You can also do without computed property and inline the object.
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
Shown in the docs on V-Bind - https://v2.vuejs.org/v2/api/#v-bind
You could build it like...
comp: { component: 'ComponentName', props: { square: true, outlined: true, dense: true }, model: 'form.bar' }
<component :is="comp.component" v-bind="{...comp.props}" v-model="comp.model"/>
I have the same challenge, fixed by the following:
<component :is="currentComponent" v-bind="resetProps">
{{ title }}
</component>
and the script is
export default {
…
props:['title'],
data() {
return {
currentComponent: 'component-name',
}
},
computed: {
resetProps() {
return { ...this.$attrs };
},
}
<div
:color="'error'"
:onClick="handleOnclick"
:title="'Title'"
/>
I'm came from reactjs and I found this solve my issue
If you have imported you code through require
var patientDetailsEdit = require('../patient-profile/patient-profile-personal-details-edit')
and initalize the data instance as below
data: function () {
return {
currentView: patientDetailsEdit,
}
you can also reference the component through the name property if you r component has it assigned
currentProperties: function() {
if (this.currentView.name === 'Personal-Details-Edit') {
return { mode: 'create' }
}
}
When you use the <component> inside a v-for you can change the answer of thanksd as follow:
methods: {
getCurrentProperties(component) {
if (component === 'myComponent') {
return {foo: baz};
}
}
},
usage
<div v-for="object in object.items" :key="object._your_id">
<component :is="object.component" v-bind="getCurrentProperties(object.component)" />
</div>
Let me know if there is an easier way.

Access to props inside javascript

I just start to learn vuejs 2 but I have an issue, I'm passing a props to a child component like this :
<div class="user">
<h3>{{ user.name }}</h3>
<depenses :user-id="user.id"></depenses>
</div>
And here is how I use it in my child component :
export default {
name: 'depenses',
props: ['userId'],
data () {
return {
depenses: []
}
},
mounted: function () {
this.getDepenses()
},
methods: {
getDepenses: function () {
this.$http.get('myurl' + this.userId).then(response => {
this.depenses = response.body
this.haveDepenses = true
}, response => {
console.log('Error with getDepenses')
})
}
}
}
this.userId is undefined but I'm able to display it with <span>{{ userId }}</span> and I can see in the vuejs console the param userId with the value
Why I have an undefined value in the js ?
Ok I found why I couldn't get my value, thanks to #Saurabh comment, he reminds me that I get my props from an async way, so I had to set a watcher when my props change, like this :
watch: {
userId: function () {
this.getDepenses()
}
},

Categories

Resources