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);
}
}
Related
I am trying to have a child component update its props that were passed from the parents at the start of the rendering. Since the value is coming from a fetch call, it takes a bit of time to get the value, so I understand that the child component will receive a 'null' variable. But once the fetch call is completed, the value is updated but the child component still has the original null value.
During my search for a solution, I found that another way was to use Vuex Stores, so I implemented it with the count variable and had a button to call a commit and later dispatch with an action function to the store to increment it's value but when the increment happens, it doesn't show the new value on the screen even though with console logs I confirmed it did change the value when the function was called.
I guess I don't fully understand how to update the value of a variable without reassigning it within it's own component or having to call a separate function manually right after I change the value of a data variable.
App.vue
<template>
<div id="app">
<div id="banner">
<div>Title</div>
</div>
<p>count: {{count}}</p> // a small test i was doing to figure out how to update data values
<button #click="update">Click </button>
<div id="content" class="container">
<CustomDropdown title="Title Test" :valueProps="values" /> // passing the data into child component
</div>
</div>
</template>
<script>
import CustomDropdown from './components/CustomDropdown.vue'
export default {
name: 'App',
components: {
CustomDropdown,
},
data() {
return {
values: null
count: this.$store.state.count
}
},
methods: {
update() {
this.$store.dispatch('increment')
}
},
async created() {
const response = await fetch("http://localhost:3000/getIds", {
method: 'GET',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
});
const data = await response.json();
this.values = data // This is when I expect the child component to rerender and show the new data. data is an array of objects
console.log("data", data, this.values) // the console log shows both variables have data
}
}
</script>
CustomDropDown.vue
<template>
<div id="dropdown-container" class="">
<b-dropdown class="outline danger" variant="outline-dark" :text="title" :disabled="disabled">
<b-dropdown-item
v-for="value in values"
:key="value.DIV_ID"
href="#">
{{value.name}}
</b-dropdown-item>
</b-dropdown>
</div>
</template>
<script>
export default {
name: 'CustomDropdown',
components: {},
props: {
title: String,
valuesProp: Array,
disabled: Boolean
},
data() {
return {
values: this.valuesProp
}
},
methods: {
},
created() {
console.log("dropdown created")
console.log(this.valuesProp) //Always undefined
}
}
</script>
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state() {
return {
count: 0,
divisionIds: []
}
},
mutations: {
increment (state) {
console.log("count", state.count)
state.count++
}
},
actions: {
increment (state) {
console.log("count action", state.count)
state.commit('increment')
}
}
})
data in your child component CustomDropdown.vue is not reactive: therefore the value of this.values is not updated when the prop changes. If you want to alias a prop, use computed instead:
export default {
name: 'CustomDropdown',
components: {},
props: {
title: String,
valuesProp: Array,
disabled: Boolean
},
computed: {
values() {
return this.valuesProp;
}
},
created() {
console.log("dropdown created");
}
}
If you want to console log the most updated values of this.valuesProp, you will need to watch it: the same if you want for this.values.
One thing you can do is to use a v-if in your child component to only render it after you get your result from you api.
It would be something like:
<CustomDropdown title="Title Test" :valueProps="values" v-if="values"/>
This way you would make sure that your child component gets rendered only when values are available.
It would only be a bad solution if this api call took so long and you needed to display the child component data to the user before that.
Hey you can simply watch it your child component
watch: { valuesProp: function(newVal, oldVal) { // watch it if(newVal.length > 0) do something }
it will watch for the value changes and when you get your desired value you can perform whatever hope it will help you you dont need store or conditional binding for it.
How can I detect changes of the getter value inside the template?
I have the following component:
computed: {
...mapGetters({
processingStep: 'products/processingStep',
}),
<div class="col" v-if="processingStep !=='last'">
...
...
</div>
So when I click the button in the Vuex state value for processingStep is getting changed during the time. The thing is that inside Vue dev tools I see updated getter value but my componnet template does not track it. How can this be fixed and how can above div be aware about processingStep value change?
You should be able to subscribe to your store's mutation. Subscribing is essentially like creating an event listener for any time a mutation is called on your store. For example, if your processingStep value is changed by a mutation called setProcessingStep:
export default {
data() {
return {
processingStep: null
}
},
mounted() {
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'setProcessingStep') {
this.processingStep = state.processingStep
}
})
}
}
This is a basic example on how this should work. I tried to mock your setup.
Below is a working example of a getter being reactive to change the vue v-if in the DOM.
Maybe this example helps you spot an error you might have made in your code.
I will not add the example using a watcher, due to this being the correct way to use vuex with getters. Using a watcher would be avoiding the real problem and be considered bad practice.
I also suspect you might have broken the vue reactivity in your app.
If you look at the vuex docs: Mutations Follow Vue's Reactivity Rules and Vue Change Detection Caveats
This essentially means that Vue cannot detect changes applied for objects and arrays done in a specific way.
For objects: Typically when you add foreign keys that was not there on initialization
For Arrays: When you directly set an item with the index or change the length
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
products: {
strict: true,
namespaced: true,
state: {
step: 'first'
},
getters: {
processingStep(state) {
return state.step;
}
},
mutations: {
CHANGE_STEP(state) {
state.step = 'last'
}
}
}
}
});
const demo = new Vue({
el: '#demo',
store: store,
computed: {
...Vuex.mapGetters({
processingStep: 'products/processingStep'
})
},
methods: {
changeStep() {
this.$store.commit('products/CHANGE_STEP')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<div id="demo">
<p>processingStep: {{ processingStep }}</p>
<button #click="changeStep">change Step</button>
<div class="col" v-if="processingStep !=='last'">
First step! :D
</div>
<p v-else-if="processingStep !== 'first'">
This is the last..
</p>
</div>
(I'm new to vue and nuxt).
I currently have a <HeaderImage> component in my layouts/default.vue and would like to have each page to pass a different image url to that component.
Right now I'm using vuex $store for that purpose (but would love if there were a simpler way to pass the data), but I'm trying to figure out where in my pages/xyz.vue I should be using the mutation this.$store.commit('headerImg/setHeaderImage', 'someImage.jpg')
All of the examples I can find only use mutations on user events.
What you are trying to do probably doesn't have a particularly simple solution and how I would do it is use a store state element that is set by the component when it is loaded. The component would commit a mutation in the store that alters the state element. The layout would then use that state element through a getter to set the image url. Here is how I'd code that. In the store state i'd have an array of class names, let's call it 'headState', and an element that would be assigned one of those class names, called 'headStateSelect:
//store/index.js
state: {
headState: ['blue', 'red', 'green'],
headStateSelect : ''
}
In your component you can use fetch, or async fetch to commit a mutation that will set 'headStateSelect' with one of the 'headState' elements.
//yourComponent.vue
async fetch ({ store, params }) {
await store.commit('SET_HEAD', 1) //the second parameter is to specify the array position of the 'headState' class you want
}
and store:
//store/index.js
mutations: {
SET_HEAD (state, data) {
state.headStateSelect = state.headState[data]
}
}
In the store we should also have a getter that returns the 'headStateSelect' so our layout can easily get it.
getters: {
head(state) {
return state.headStateSelect
}
}
finally, in the layout we can use the computed property to get our getter:
//layouts/default.vue
computed: {
headElement() {
return this.$store.getters.head
}
}
and the layout can use the computed property to set a class like so:
//layouts/default.vue
<template>
<div :class="headElement">
</div>
</template>
The div in the layout will now be set with the class name 'red' (ie. store.state.headState[1]) and you can have a .red css class in your layout file that styles it however you want, including with a background image.
For now I've settled on creating it like this:
~/store/header.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = () => ({
headerImage: 'default.jpg'
})
const mutations = {
newHeaderImage(state, newImage) {
state.headerImage = newImage
}
}
export default {
namespaced: true,
state,
mutations
}
``
~/layouts/default.vue
<template>
<div id="container">
<Header />
<nuxt />
</div>
</template>
<script>
import Header from '~/components/Header'
export default {
components: {
Header
}
}
</script>
``
~/components/Header.vue
<template>
<header :style="{ backgroundImage: 'url(' + headerImage + ')'}" class="fixed">
<h1>Header Text</h1>
</header>
</template>
<script>
computed: {
var image = this.$store.state.header.headerImage
return require('~/assets/img/' + image)
}
</script>
``
~/pages/customHeader.vue
<template>
<main>
...
</main>
</template>
<script>
export default {
head() {
this.$store.commit('header/newHeaderImage', 'custom-header.jpg')
return {
title: this.title
}
}
}
</script>
But something feels off about putting the mutation in head() Is that correct?
And the next issue I am facing is how to return the header to default.jpg if a page doesn't change the state (which makes me think this is all the wrong approach).
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.
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.