I'm trying to access this.$el.offsetTop in a computed property, but get the error:
Cannot read property 'offsetTop' of undefined
How can I access the offsetTop of the component in a computed method? if this is not possible then is there an alternative?
Component:
<template>
<div :class="{ 'active' : test }"></div>
</template>
<script>
export default {
computed: {
test() {
console.log(this.$el.offsetTop);
}
}
}
</script>
If a computed property is used in the template, its method will fire before the Vue instance is mounted, so this.$el will be undefined.
If your computed property is dependant on some property of this.$el, you can set it to a data property in the mounted hook (where this.$el will be defined).
In your case:
<template>
<div :class="{ 'active' : test }"></div>
</template>
<script>
export default {
data() {
return { offsetTop: null };
},
computed: {
test() {
console.log(this.offsetTop);
}
},
mounted() {
this.offsetTop = this.$el.offsetTop;
}
}
</script>
Here's what I did to solve this problem:
I set an el property to null in the component's data, and in the mounted hook, I set this.el to this.$el. Then, in the computed property, I check this.el before trying to access its properties to avoid an error during the first render. So, in the context of your example we'd have:
<template>
<div :class="{ 'active' : test }">{{test}}</div>
</template>
<script>
export default {
data: {
el: null,
},
computed: {
test() {
return this.el ? this.el.offsetTop : 0;
}
},
mounted(){
this.el = this.$el;
}
}
</script>
As a side note, I don't believe you have access to the console in Vue.js computed properties, so I removed that bit.
Related
I'm building web app with Vue, Nuxt, and Element UI.
I have a problem with the Element dialog component.
It can open for the first time, but it can't open for the second time.
This is the GIF about my problem.
https://gyazo.com/dfca3db76c75dceddccade632feb808f
This is my code.
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first :visible=visible></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
ModalFirst.vue
<template>
<el-dialog
title="Tips"
:visible.sync="visible"
width="30%"
>
<span>This is a message</span>
<span slot="footer" class="dialog-footer">
<a>Hello</a>
</span>
</el-dialog>
</template>
<script>
export default {
props: [ 'visible' ]
}
</script>
And I can see a warning message on google chrome console after closing the dialog.
The warning message is below.
webpack-internal:///./node_modules/vue/dist/vue.runtime.esm.js:620 [Vue warn]: 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: "visible"
found in
---> <ModalFirst> at components/ModalFirst.vue
<Pages/index.vue> at pages/index.vue
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
This is the screenshot of the warning message.
https://gyazo.com/83c5f7c5a8e4d6816c35b3116c80db0d
In vue , using directly to prop value is not allowed . Especially when your child component will update that prop value , in my option if prop will be use
for display only using directly is not a problem .
In your code , .sync will update syncronously update data so I recommend to create local data.
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'visible' ],
data: function () {
return {
localVisible: this.visible // create local data using prop value
}
}
}
</script>
If you need the parent visible property to be updated, you can create your component to leverage v-model:
ModalFirst.vue
<el-dialog
title="Tips"
:visible.sync="localVisible"
width="30%"
>
<script>
export default {
props: [ 'value' ],
data() {
return {
localVisible: null
}
},
created() {
this.localVisible = this.value;
this.$watch('localVisible', (value, oldValue) => {
if(value !== oldValue) { // Optional
this.$emit('input', value); // Required
}
});
}
}
</script>
index.vue
<template>
<div>
<el-button type="text" #click="handleDialogVisible">click to open the Dialog</el-button>
<modal-first v-model="visible"></modal-first>
</div>
</template>
<script>
import ModalFirst from './../components/ModalFirst.vue'
export default {
components: {
'modal-first': ModalFirst
},
data() {
return {
visible: false,
};
},
methods: {
handleDialogVisible() {
this.visible = true;
}
}
}
</script>
v-model is basically a shorthand for :value and #input
https://v2.vuejs.org/v2/guide/forms.html#Basic-Usage
Side-note:
You can also import your component like so:
components: { ModalFirst },
as ModalFirst will be interpreted as modal-first as well by Vue.js
I am using Single File Components and I have a modal component that has an
input box but I can't get the value of the input in a function below using the v-modal name. It keeps coming back as 'name is not defined'. Am I using the v-model attribute incorrectly?
<template>
<input v-model="name" class="name"></input>
</template>
<script>
export default {
methods: {
applyName() {
let nameData = {{name}}
}
}
}
</script>
You're right, you're using the v-model property incorrectly.
First off you need to define a piece of state in your component, using data:
export default {
data: () => ({
name: '',
}),
methods: {
log() {
console.log(this.name);
}
}
}
You can then bind this piece of data in your component using v-model="name", just like you did. However, if you want to access this piece of state in your method, you should be using this.name in your applyName() method.
Your {{name}} syntax is used to get access to the data in your template, like so:
<template>
<span>
My name is: {{name}}!
</span>
</template>
You have to use this pointer to access the model:
<template>
<input v-model="inputName" class="name"></input>
</template>
<script>
export default {
data() {
return {
inputName: '',
}
},
methods: {
applyName() {
// Notice the use of this pointer
let nameData = { name: this.inputName };
}
}
}
</script>
Look at the doc https://v2.vuejs.org/v2/guide/forms.html#v-model-with-Components
In the template, you are referring by name to data, computed or methods. In this case, it refers to data. When the input changes the name then the data is updated.
It is possible to use in a function referring to this.
<template>
<input v-model="name" class="name"></input>
</template>
<script>
export default {
data() {
return { name: '' }
},
methods: {
applyName() {
let nameData = this.name
}
}
}
</script>
I have a vuex store. on change of state preference in the vuex store. i want to rerender the DOM. i want the checkValue method to be called everytime the state preference in the vuex store changes.
index.html
<div id="app">
<my-component></my-component>
<my-other-component></my-other-component>
</div>
vue is initialised, and also store is imported here
my_component.js
Vue.component('my-component',require('./MyComponent.vue'));
import store from "./store.js"
Vue.component('my-other-component',require('./MyOtherComponent.vue'));
import store from "./store.js"
new Vue({
el : "#app",
data : {},
store,
method : {},
})
component where DOM needs to be change on change of the state preference in store
MyComponent.vue
<template>
<div v-for="object in objects" v-if="checkValue(object)">
<p>hello</p>
</div>
</template>
<script>
methods : {
checkValue : function(object) {
if(this.preference) {
// perform some logic on preference
// logic results true or false
// return the result
}
}
},
computed : {
preference : function() {
return this.$store.getters.getPreference;
}
}
</script>
Vuex store file
store.js
const store = new Vuex.Store({
state : {
preferenceList : {components : {}},
},
getters : {
getPreference : state => {
return state.preferenceList;
}
},
mutations : {
setPreference : (state, payload) {
state.preference['component'] = {object_id : payload.object_id}
}
}
component from where the vuex store is updated on clicking in the li element.
MyOtherComponent.vue
<div>
<li v-for="component in components" #click="componentClicked(object)">
</li>
</div>
<script type="text/javascript">
methods : {
componentClicked : function(object) {
let payload = {};
payload.object_id = object.id;
this.$store.commit('setPreference', payload);
}
}
</script>
Methods are not reactive,
which means they will not track changes and re-run when something
changes. That's what you have computed for.
So it means you need to use a computed to calculate what you need, but computed does not accept parameters and you need the object, so the solution is to create another component that accepts the object as a property and then perform the logic there:
MyOtherComponent.vue:
<template>
<div v-if="checkValue">
<p>hello</p>
</div>
</template>
<script>
props:['object','preference']
computed : {
checkValue : function() {
if(this.preference) {
// perform some logic on preference
// logic results true or false
return true
}
return false
}
}
</script>
And then in the original component:
<template>
<my-other-component v-for="object in objects" :object="object" :preference="preference">
<p>hello</p>
</my-other-component>
</template>
v-if should not contain a function call. Just the existence of the function will likely cause the v-if to always be true. v-if should test a variable or a computed property, and it should have a name that's a noun, not a verb ! If checkValue just proxies preference, why do you need it. Why not just v-if="preference" ?
I think your main issue is your mutation: VueJS creates everything it needs for reactivity during initialization, so your state.components object is already initialized when you try to override it with a new object with your mutation payload, which will then not be configured for reactivity (see https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats).
Try changing your mutations to:
mutations: {
setPreference (state, payload) {
Vue.set(state.preferenceList.components, 'object_id', payload.object_id);
}
}
I'm using Vue-stash as an alternative to vuex. Vue-stash itself is reactive. However, if I use it inside a data variable, that variable isn't changing
<template>
<div>
{{id}} // not reactive
</div>
</template>
<script>
export default {
data() {
return {
id: this.$store.id
}
}
}
</script>
A Vue instance's data property only gets set once on instantiation.
If you want the id to always reflect the value of this.$store.id, you should use a computed property:
export default {
computed: {
id() {
return this.$store.id;
}
}
}
I am passing a variable from parent component to child component through props. But with some operation, the value of that variable is getting changed i.e. on click of some button in parent component but I did not know how to pass that updated value to child? suppose the value of one variable is false initially and there is Edit button in parent component. i am changing the value of this variable on click of Edit button and want to pass the updated value from parent to child component.
Your property's value should be updated dynamically when using props between parent and child components. Based on your example and the initial state of the property being false, it's possible that the value was not properly passed into the child component. Please confirm that your syntax is correct. You can check here for reference.
However, if you want to perform a set of actions anytime the property's value changes, then you can use a watcher.
EDIT:
Here's an example using both props and watchers:
HTML
<div id="app">
<child-component :title="name"></child-component>
</div>
JavaScript
Vue.component('child-component', {
props: ['title'],
watch: {
// This would be called anytime the value of title changes
title(newValue, oldValue) {
// you can do anything here with the new value or old/previous value
}
}
});
var app = new Vue({
el: '#app',
data: {
name: 'Bob'
},
created() {
// changing the value after a period of time would propagate to the child
setTimeout(() => { this.name = 'John' }, 2000);
},
watch: {
// You can also set up a watcher for name here if you like
name() { ... }
}
});
You can watch a (props) variable with the vue watch.
for example:
<script>
export default {
props: ['chatrooms', 'newmessage'],
watch : {
newmessage : function (value) {...}
},
created() {
...
}
}
</script>
I hope this will solve your problem. :)
Properties, where the value is an object, can be especially tricky. If you change an attribute in that object, the state is not changed. Thus, the child component doesn't get updated.
Check this example:
// ParentComponent.vue
<template>
<div>
<child-component :some-prop="anObject" />
<button type="button" #click="setObjectAttribute">Click me</button>
</div>
</template>
<script>
export default {
data() {
return {
anObject: {},
};
},
methods: {
setObjectAttribute() {
this.anObject.attribute = 'someValue';
},
},
};
</script>
// ChildComponent.vue
<template>
<div>
<strong>Attribute value is:</strong>
{{ someProp.attribute ? someProp.attribute : '(empty)' }}
</div>
</template>
<script>
export default {
props: [
'someProp',
],
};
</script>
When the user clicks on the "Click me" button, the local object is updated. However, since the object itself is the same -- only its attribute was changed -- a state change is not dispatched.
To fix that, the setObjectAttribute could be changed this way:
setObjectAttribute() {
// using ES6's spread operator
this.anObject = { ...this.anObject, attribute: 'someValue' };
// -- OR --
// using Object.assign
this.anObject = Object.assign({}, this.anObject, { attribute: 'someValue' });
}
By doing this, the anObject data attribute is receiving a new object reference. Then, the state is changed and the child component will receive that event.
You can use Dynamic Props.
This will pass data dynamically from the parent to the child component as you want.