Computed properties are not reacting to state change - vue.js

I am working on a product overview page, that send out an API-call based on the current Category you are looking at:
store.dispatch("tweakwise/fetchAPIAttributesLayeredNavigation", {
tweakwiseCategory,
this.pageNumber,
}
In my Store, the data from this API-call will be set in the following VueX Store State:
this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes: []
I want to react to this data in my front-end but my Computed methods do not seem to react to this change. As you can also see in the function below I added a Catch to prevent a "Non defined" error. The function, however, will not be called after the state has been set.
This computed property is also added to the Mount() op the component
computed: {
initialFetchProducts() {
this.fetchProducts(
this.$store.state.tweakwise?.tweakwiseLayeredNavigationAttributes || []
);
},
},

make computed property for state you want to watch,
than create watch() for this prop. In watch you can react on computed property change.
<template>
<div v-for="product in products"></div>
</template>
<script>
export default {
data: {
return {
products: [],
}
},
computed: {
tweakwiseLayeredNavigationAttributes() {
return this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes;
},
},
watch: {
// on every tweakwiseLayeredNavigationAttributes change we call fetchProducts
tweakwiseLayeredNavigationAttributes: {
handler(newValue, oldValue) {
this.fetchProducts(newValue);
},
deep: true, // necessary for watching Arrays, Object
immediate: true, // will be fired like inside mounted()
}
},
methods: {
async fetchProducts(params) {
const products = await axios.get('/api', params);
this.products = products;
}
}
};
</script>

Related

Provide/inject axios response

I have one component in which I use axios to get data from API, it works and I can use this data in this component, but when I try to provide this data to another component, I dont get any data there.
Here is part of my code:
data() {
return {
theme: [],
};
},
provide() {
return {
theme: this.theme
}
},
methods: {
getTheme() {
axios
.get(here is my api url)
.then((response) => (this.theme = response.data.data));
},
},
mounted() {
this.getTheme();
},
and this is the second component:
<template>
<div class="project-wrapper">
<project-card
v-for="course in theme.courses"
:name="course.name"
:key="course.id"
></project-card>
</div>
</template>
<script>
import ProjectCard from "../components/ProjectCard.vue";
export default {
inject: ["theme"],
components: {
ProjectCard,
}
};
</script>
What is wrong with my code?
Second option in the link may help you
provide() {
return {
$theme: () => this.theme,
}
},
and
inject: ["$theme"],
computed: {
computedProperty() {
return this.$theme()
}
}
and
v-for="course in computedProperty.courses"
When you set provide to 'handle' theme it adds reactivity to the value of theme - i.e the empty array ([]).
If you modify the elements in this array, it will remain reactive - however if you replace the array then the reactivity is broken.
Instead of overwriting theme in the axios call, try adding the resulting data to it. For example:
getTheme() {
axios
.get(here is my api url)
.then((response) => (this.theme.push(...response.data.data));
}
You are passing theme to the child component as injected property.
See Vue.js Docs:
The provide and inject bindings are NOT reactive. This is intentional.
However, if you pass down an observed object, properties on that
object do remain reactive.
As inject bindings are not reactive, the changed value of theme will not be visible from inside of the child component (it will stay the same as if no axios call happened).
Solution 1
Pass the value to the child component as an observed object. It means that in in your getTheme() method you will not rewrite the whole property value (this.theme = ...) but only write into the object which is already stored in the property (this.theme.themeData = ...).
data() {
return {
theme: { },
};
},
provide() {
return {
theme: this.theme
}
},
methods: {
getTheme() {
axios
.get(here is my api url)
.then((response) => (this.theme.themeData = response.data.data));
},
},
mounted() {
this.getTheme();
}
Solution 2
Alternatively you can pass the value to the child component using classical props which are always reactive.

Getting notified if the data is loaded when the data is in a computed field

I have a Vue project with vuetify, and i want to use the loading functionality.
The project is set up so the items are loaded with a computed field.
How do i now manipulate the 'loading' property, turning it of when its loaded?
I tried attaching the items async fetching the data with a non-computed field. But the v-data-table is turned into a component and i'm not succeeding.
<data-table
:title="title"
:headers="headers"
:items="allUsers"
:loading="loading"
#editItem="onEdit"
#deleteItem="onDelete" >
</data-table>
The allUsers property is computed
Right now the loaded property is also computed putting a .length on the allusers. This only works if there are users ... if none it keeps spinning.
computed: {
allUsers () {
return this.$store.getters['users/users']
},
loading(){
return this.$store.getters['users/users'] != null;
}
},
Assuming you load users into your store within an action, you should also set some state for loading.
For example (simplified, no module)
const store = {
state: {
users: [],
loading: false
},
mutations: {
setUsers (state, users) {
state.users = users
},
isLoading (state) {
state.loading = true
},
loadingComplete (state) {
state.loading = false
}
},
actions: {
async loadUsers ({ commit }) {
commit('isLoading') // set loading state
commit('setUsers', await fetchUserDataFromSomwhere())
commit('loadingComplete')
}
}
}
Now your components can subscribe to the loading state
computed: {
loading () {
return this.$store.state.loading // or use mapState / mapGetters / whatever
},
allUsers () {
return this.$store.state.users
}
}

VueJS: Computed Property Is Calculated Before Created in Component?

I have a component, which looks like this:
export default {
name: 'todos',
props: ['id'],
created () {
this.fetchData()
},
data() {
return {
}
},
computed: {
todos () {
return this.$store.state.todos[this.id]
}
},
methods: {
async fetchData () {
if (!this.$store.state.todos.hasOwnProperty(this.id)) {
await this.$store.dispatch('getToDos', this.id)
}
}
}
}
This is what's happening:
The component receives an id via props.
When the component loads I need to fetch some data based on the id
I have a created() hook from where I call a function fetchData() to fetch the data.
In methods, the fetchData() function dispatches an action to get the data. This gets and stores the data in Vuex store.
The computed property todos gets the data for this id.
The problem is that when the page first loads, the computed property todos shows up as undefined. If I change the page (client side) then the computed property gets the correct data from the store and displays it.
I am unable to understand why computed property doesn't update?
You could use following approach:
component.vue (and just render todoItem)
methods: {
async fetchData () {
const _this = this;
if (!this.$store.state.todos.hasOwnProperty(this.id)) {
this.$store.dispatch('getToDos', {id: this.id, callback: () => {
_this.todoItem = _this.$store.state.todos[_this.id]
}});
}
}
}
store.js
actions: {
getToDos: (context, payload) => {
// simulate fetching externally
setTimeout(() => {
context.commit("getToDos__", {newId: payload.id, task: "whatever" });
payload.callback();
}, 2000);
},
Base on here
When this hooks is called, the following have been set up: reactive data, computed properties, methods, and watchers. However, the mounting phase has not been started, and the $el property will not be available yet.
I think what might solve it is if you create a getter for todos.
So in your VueX Store add:
getters: {
todos(state) {
return state.todos;
}
};
And than in your computed use:
computed: {
todos () {
return this.$store.getters.todos[this.id]
}
}

How to update Vuex store from v-model input in case of v-for

I've say 10 objects in an array like
policies = [{name:'a',text:''},{name:'b',text:''},....]
They're iterated using v-for to show label A: Inputbox with text property binded as v-model.
I want to trigger a mutation whenever a policy's text changes in v-model.
Here's the fiddle link for it.
https://jsfiddle.net/dmf2crzL/41/
We assume you want to use v-model for a 2-way binding along with Vuex store.
Your problem is that you want Vuex store in strict mode.
const store = new Vuex.Store({
// ...
strict: true
})
so all of your mutation should go through Vuex store and you can see it in Vue.js devtools.
Method 1: We can avoid the Vuex error by using the cloned object and use watcher to commit the mutation.
const store = new Vuex.Store({
strict: true,
state: {
formdata: [
{ label: 'A', text: 'some text' },
{ label: 'B', text: 'some other text' },
{ label: 'C', text: ' this is a text' }
]
},
mutations: {
updateForm: function (state, form) {
var index = state.formdata.findIndex(d=> d.label === form.label);
Object.assign(state.formdata[index], form);
}
}
});
new Vue({
el: '#app',
store: store,
data () {
return {
//deep clone object
formdata: JSON.parse(JSON.stringify(this.$store.state.formdata))
};
},
computed: {
formdata() {
return this.$store.state.formdata
}
},
watch: {
formdata: function(form)
this.$store.commit('updateForm', form);
}
}
})
Method 2: You can use computed get/set to commit your mutation as per the vuex doc
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
another way that I found useful:
replace the v-model to a (v-on) function
that function triggers a mutation
the mutation ("function" in the store) change a value in state
a getter ("computed" in the store) "listens" to the change in the property value and changes accordingly.
this is an example of how to filter cards with Vuex (instead of v-model:
input that triggers a function "updateFilter":
<input type="text" placeholder="filter" v-on:input='updateFilter'>
a function (method) that triggers a mutation (commit):
methods: {
updateFilter(event){
this.$store.commit('updateFilter', event.target.value);
}
in the store.js, a mutation that changes data (state):
mutations: {
updateFilter (state, filter) {
state.filter = filter;
},
the state:
state: {filter: ""}
and the getter (computed) that "listens" to the change in the state.
getters: {
filteredGames: state => {
//your filter code here
return filtered;
})
},
and finally, the component that needs to be filtered has this computed (getter):
computed: {
filtered() {
return this.$store.getters.filteredGames;
}
Mine library vuex-dot simplifies reactivity (and, sure, v-model) usage on vuex store
https://github.com/yarsky-tgz/vuex-dot
<template>
<form>
<input v-model="name"/>
<input v-model="email"/>
</form>
</template>
<script>
import { takeState } from 'vuex-dot';
export default {
computed: {
...takeState('user')
.expose(['name', 'email'])
.dispatch('editUser')
.map()
}
}
</script>

VueJS - Accessing store data inside mounted

I'm having trouble understanding the following:
I have a store which contains variables needed for the application. In particular, there is a globalCompanies which stores:
globalCompanies: {
current: [],
all: [],
currentName: "",
}
Inside another component, I want to do the following:
mounted() {
this.$store.dispatch( "fetchUsers" );
var currentName = this.$store.state.globalCompanies.currentName;
console.log(currentName);
},
However, this just shows as empty. I know the value is there because I have computed which returns the currentName and it works fine inside the view itself. It just doesn't like the fact that it's in the mounted component.
Where am I going wrong and what can I do to resolve this issue? I really need to capture the companies Name in order to use it for some real time events.
As a result of our discussion:
In the question Vuex state value, accessed in component's mounted hook, returns empty value, because it is set in an async action which does not resolve before mounted executes.
When you need to trigger some function when async action in Vuex resolves with a value, you can achieve it using watch on a computed property, which returns a value from your Vuex state. When a value in store changes, the computed property reflects these changes and watch listener executes:
const store = new Vuex.Store({
state: {
globalCompanies: {
test: null
}
},
mutations: {
setMe: (state, payload) => {
state.globalCompanies.test = payload
}
},
actions: {
pretendFetch: ({commit}) => {
setTimeout(() => {
commit('setMe', 'My text is here!')
}, 300)
}
}
})
new Vue({
el: '#app',
store,
computed: {
cp: function() { // computed property will be updated when async call resolves
return this.$store.state.globalCompanies.test;
}
},
watch: { // watch changes here
cp: function(newValue, oldValue) {
// apply your logic here, e.g. invoke your listener function
console.log('was: ', oldValue, ' now: ', newValue)
}
},
mounted() {
this.$store.dispatch('pretendFetch');
// console.log(this.cp, this.$store.state.globalCompanies.test); // null
// var cn = this.$store.state.globalCompanies.test; // null
// console.log(cn) // null
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<script src="https://unpkg.com/vuex#2.3.1"></script>
<div id="app">
{{ cp }}
</div>
VueJS - Accessing Store Data Inside Mounted
Ran into this issue and it turned out to be a scope issue.
Store:
export default () => {
items:[],
globalCompanies:{
current:[],
all:[],
currentName: "Something"
},
ok: "Here you go"
}
Getters:
export default {
getGlobalCompanies(state){
return state.globalCompanies;
}
}
Mounted: This works...
mounted() {
// Initialize inside mounted to ensure store is within scope
const { getters } = this.$store;
const thisWorks = () => {
const globalCompanies = getters.getGlobalCompanies;
}
},
This is Bad: Reaching for the store outside the mounted scope
mounted() {
function ThisDontWork() {
const { getters } = this.$store; // this.$store == undefined
}
ThisDontWork();
},