Vuejs prop does not get updated - javascript

I have following scenario. I have this component
<div class="flex items-start gap-4">
<div class="flex w-full flex-col gap-4">
<div class="border shadow p-2">
<chart :config="lineConfig" ref="linechart" />
</div>
</div>
<options
:chart="$refs.linechart"
:resolution="lineResolution"
:maxTicksLimit="lineMaxTicksLimit"
></options>
</div>
In my options.vue
export default Vue.extend({
props: ['resolution', 'maxTicksLimit', 'chart'],
watch: {
resolution() {
this.chart.update()
},
maxTicksLimit() {
this.chart.update()
},
},
created() {
setTimeout(() => {
console.log(this.chart)
}, 100)
},
})
I always gets undefined. I understand that the component did not mounted yet and i would need to use $nextTick(), but if i pass :chart="$refs" then i see in the console { linechart: ... }
Also a sidenote: If i interact with the chart, for example using chart.update(), then this.chart is no more undefined.
My goal is to pass the chart with $refs.linechart so i can use the methods of the component

From Vue docs
An important note about the ref registration timing: because the refs
themselves are created as a result of the render function, you cannot
access them on the initial render - they don’t exist yet! $refs is
also non-reactive, therefore you should not attempt to use it in
templates for data-binding.
So that explains pretty much the entire behaviour. If you are binding $this.linechart it will pass undefined at first render because the ref doesn't exist yet. If you are binding $refs you pass a reference to $refs object which latter is updated in a non-reactive way.
I can think only of one solution. Render the options component only after the parent has been rendered, to have $refs object populated, and don't count on its reactivity. Like:
<options
v-if="rendered"
:chart="$refs.linechart"
:resolution="lineResolution"
:maxTicksLimit="lineMaxTicksLimit"
></options>
data(){
rendered: false
},
mounted() {
this.$nextTick(() => this.rendered = true)
}

Related

Props being mutated in VueJS

I am new to vue and I am getting this error, I am not sure if I am passing the props right and executing it well in the other component. I will explain in details what I am trying to acheive.
I am hiding a component on click on this page and showing another element on click interchangeably here.
I have read a couple of solutions but I do not understand how I'm exactly suppose to fix it
<div v-if="hidden" class="orderSummary">
<div class="orderSummary__container">
<h2 class="orderSummary__header">Order Summary</h2>
<button #click="showForm()" class="total__button">Continue</button>
<PaymentForm v-if="!hidden" :hidden="hiddenMode" />
</div>
</div>
methods: {
showForm() {
if (this.subTotal > 1) {
this.hidden = false;
}
}
},
now in the Payment Form component I need to hide the component and make the other appear also, i want to do this by passing props.
This is my code
<div class="payForm">
<div #click="hideForm()" class="PayForm__icon">
<backIcon class="icon" />
<span class="PayForm__icon-text">Go back</span>
</div>
</div>
props: ["base_amount", "value_added_tax", "hiddenMode"],
methods: {
submit() {
const data = {
name: this.name,
};
},
hideForm() {
this.hiddenMode = true;
}
},
I'm getting the error below, what do I do
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "hiddenMode"
Don't make hiddenMode a prop; set it to state through data:
//parent
props: ["base_amount", "value_added_tax"],
data() {
return {
hiddenMode: false
}
},
...
edit:
You should also move hideForm() to the parent component and instead bind to PaymentForm's onlick:
#click="$emit('clicked')"
then in the parent component bind hideForm to the clicked emit:
<PaymentForm v-if="!hidden" :hidden="hiddenMode" #clicked="hideForm"/>
Note: the $emit doesn't have to be called "clicked" you can name it anything
First, there is a logical problem here. If hidden is false then the first DIV and children including PaymentForm are not existing.
If hidden is true the PaymentForm not showing too because you have a <PaymentForm v-if="!hidden"
Second, your PaymentForm has a hiddenMode prop and you don't set it in the parent vue. You should have :hiddenMode="hidden" and not :hidden="hiddenMode"
For you hideForm function use $emit
this.$emit('update:hiddenMode', true);
Use the .sync modifier. This way the child component does not modify the property directly. So the parent would be
<PaymentForm v-if="!hidden" :hidden-mode.sync="hiddenMode" />
and the child would be
hideForm() {
this.$emit('update:hiddenMode', true);
}

Vue component not updating when data changes without :key

I have this simple breadcrumbs component inside my app. There is a data property pickedTable but when it changes the component doesn't re-render. But when I add the :key="pickedTable" then it re-renders. Why is this happening?
Has anyone else experienced this issue?
export default {
template: `
<div class="cr-snackbar">
<div class="cr-snackbar-selection">
Table {{ pickedTable }}
</div>
</div>
`,
data()
{
return {
pickedTable: '2',
}
},
mounted()
{
setInterval(() => {
this.pickedTable = '3'
}, 3000)
}
}
My solution was to add the key
<div class="cr-snackbar-selection" :key="pickedTable">
Table {{ pickedTable }}
</div>
This is not an issue but a feature.
Vue uses an algorithm that minimizes element movement and tries to patch/reuse elements of the same type in-place as much as possible. With keys, it will reorder elements based on the order change of keys, and elements with keys that are no longer present will always be removed/destroyed.
Here is the official doc : https://v2.vuejs.org/v2/api/#key
And here is a nice article on component re-rendering : https://michaelnthiessen.com/force-re-render/

Vuejs moving data between multiple components

I have a parent component with two children components that I need data to move between. I can get emitted data from one child (multiselect.vue) to the parent, but cannot get it to then move into the other child component (render.vue). I suspect it has something to with v-model. Below is a simplifed version.
Parent component
<template>
<div>
<multiSelect v-model="content" />
<renderSplashPage :content="render" />
</div>
</template>
<script>
import multiSelect from '#/utils/multiSelect'
import render from '#/components/Render'
export default {
components: {
multiSelect,
render,
},
name: 'somename',
data(){
return{
content: {},
render: {}
}
},
watch: {
'content': function(val) {
this.render = val;
},
},
}
</script>
How can I get the emitted data to be 'watched' and 'picked up' by the 'render' component?
render is a reserved name (i.e., the internal render function cannot be overwritten), and for some reason, Vue silently ignores attempts to modify it.
Rename your property (e.g., to "myRender"), and you should see reactivity. If myRender is always equal to content, you could actually just replace it with content in your binding:
<multiselect v-model="content" :options="options" />
<render :content="content" />
demo
I used a variation of this here to solve my issue:
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
Taken from here:
https://v2.vuejs.org/v2/guide/reactivity.html

Vuex how to Access state data on component mounted or created hook?

I'm trying to create a Quill.js editor instance once component is loaded using mounted() hook. However, I need to set the Quill's content using Quill.setContents() on the same mounted() hook with the data I received from vuex.store.state .
My trouble here is that the component returns empty value for the state data whenever I try to access it, irrespective of being on mounted() or created() hooks. I have tried with getters and computed properties too. Nothing seems to work.
I have included my entry.js file, concatenated all the components to make things simpler for you to help me.
Vue.component('test', {
template:
`
<div>
<ul>
<li v-for="note in this.$store.state.notes">
{{ note.title }}
</li>
</ul>
{{ localnote }}
<div id="testDiv"></div>
</div>
`,
props: ['localnote'],
data() {
return {
localScopeNote: this.localnote,
}
},
created() {
this.$store.dispatch('fetchNotes')
},
mounted() {
// Dispatch action from store
var quill = new Quill('#testDiv', {
theme: 'snow'
});
// quill.setContents(JSON.parse(this.localnote.body));
},
methods: {
setLocalCurrentNote(note) {
console.log(note.title)
return this.note = note;
}
}
});
const store = new Vuex.Store({
state: {
message: "",
notes: [],
currentNote: {}
},
mutations: {
setNotes(state,data) {
state.notes = data;
// state.currentNote = state.notes[1];
},
setCurrentNote(state,note) {
state.currentNote = note;
}
},
actions: {
fetchNotes(context) {
axios.get('http://localhost/centaur/public/api/notes?notebook_id=1')
.then( function(res) {
context.commit('setNotes', res.data);
context.commit('setCurrentNote', res.data[0]);
});
}
},
getters: {
getCurrentNote(state) {
return state.currentNote;
}
}
});
const app = new Vue({
store
}).$mount('#app');
And here is the index.html file where I'm rendering the component:
<div id="app">
<h1>Test</h1>
<test :localnote="$store.state.currentNote"></test>
</div>
Btw, I have tried the props option as last resort. However, it didn't help me in anyway. Sorry if this question is too long. Thank you for taking your time to read this. Have a nice day ;)
I copied your code and tested it ( of-course I created my own dummy notes so I could remove the get request ) and I was able to get the notes display on a page.
A couple of things that I realized from your code, you may need to add a store property as there are places in your component ( test ) where you are referencing it, yet you only define it on the 'app' component. So in this section of your code modify as shown below:
props: ['localnote'],
data() {
return {
localScopeNote: this.localnote,
store : store
}
},
The key difference is the definition of the 'store' property. Please note that, what you have done, defining a "store" property in your app component, is correct, but the very same needs to be defined in "test" component as I have shown in the above code snippet above.
Second thing is, you are using $store and I guess that gives you undefined, unless as you said, in the libraries that you included this resolves accordingly, but on my side I had to remove all references of "$store" and replace it with just "store" (without the dollar sign).
Lastly for testing purposes, I would advise you to also

vue.js list ( template ) binding not updating when changing data from directive

First of all : I'm using laravel spark and the given setup of vue that comes with spark.
I have a "home" component with the prop "custom". Within custom there's a "passwords" array. (Entry added by code of directive, it's initialized empty)
My component ( alist) which should be bound against the data
<template id="passwords-list-template">
<div class="password" v-for="password in list">
<ul>
<li>{{ password.name }}</li>
<li>{{ password.description }}</li>
</ul>
</div>
</template>
<script>
export default {
template: '#passwords-list-template',
props: ['list'],
};
</script>
Usage
<passwords-list :list="custom.passwords"></passwords-list>
Using vue devtools I can see that my data is updating, however my list is not. Also other bindings like
<div v-show="custom.passwords.length > 0">
Are not working ...
UPDATE : Parent component (Home)
Vue.component('home', {
props: ['user', 'custom'],
ready : function() {
}
});
Usage
<home :user="user" :custom="spark.custom" inline-template>
Update 2: I played around a little bit using jsfiddle. It seems like changing the bound data object using $root works fine for me when using a method of a component. However it does not work when trying to access it using a directive
https://jsfiddle.net/wa21yho2/1/
There were a lot of errors in your Vue code. First of all, your components where isolated, there wasn't an explicit parent-child relationship.Second, there were errors in the scope of components, you were trying to set data of the parent in the child, also, you were trying to set the value of a prop, and props are by default readonly, you should have written a setter function or change them to data. And finally, I can't understand why were you trying to use a directive if there were methods and events involve?
Anyway, I rewrote your jsfiddle, I hope that you find what you need there. The chain is Root > Home > PasswordList. And the data is in the root but modified in home, the last component only show it. the key here are twoWay properties, otherwise you wouldn't be able to modify data through properties.
Here is a snippet of code
Home
var Home = Vue.component('home', {
props: {
user: {
default: ''
},
custom: {
twoWay: true
}
},
components: {
passwordList: PasswordList
},
methods: {
reset: function () {
this.custom.passwords = [];
}
}
});
// template
<home :custom.sync="spark.custom" inline-template>
{{custom | json}}
<button #click="reset">
reset in home
</button>
<password-list :list="custom.passwords"></password-list>
<password-list :list="custom.passwords"></password-list>
</home>
Here is the full jsfiddle

Categories