Vue.js, VueX - cannot render data from API in a component - vue.js

I want to make an API call to the server to fetch the data and then display them in a component. I have a created() method which dispatches an action to my store, which, in turn, commits the mutation to udpate my store with the data I got from the server. I also have computed method where I simply call the getter which fetches the data from the store. The code looks like this:
state
state: {
data: {
rides: []
}
}
component.vue
created() {
this.$store.dispatch('fetchUserRides');
}
computed: {
...mapGetters([
'userRides'
]),
}
store.js
//actions
fetchUserRides({ commit }) {
axios.get('/api/stats/current_week')
.then(response => {
commit('fetchUserRides', response)
})
.catch(error => {
commit('serverResponsError')
})
//mutations...
fetchUserRides(state, payload){
let rides = payload.data
rides.forEach((item) => {
state.data.rides.push(item)
})
//getters
userRides: state => {
let rides = state.data.rides
rides.sort(( a, b) => {
return new Date(a.date) - new Date(b.date);
});
return rides
}
I receive over 40 objects in the response, I did check it by console.log(state.data.rides) and they are there in 100%.
My problem is that when I log off and log back in again it throws an error "TypeError: Cannot read property 'sort' of null". But if I hit Refresh they appear fine. The login action redirects me to the page where I render this component. This looks like the computed property first tries to fetch data by the getter from the array before it is actually populated in the store. How can I make sure I get the array of objects in my component?

You probably need to set an empty array ([]) as an initial value to state.data.rides instead of null.
Another option will be to check that rides is truthy in your getters.
Something like:
if (rides) {
rides.sort(( a, b) => {
return new Date(a.date) - new Date(b.date);
});
}
return []

I was able to resolve my problem and it turns out I made a mistake. I completely forgot I set state.data.rides = null instead of an empty array state.data.rides = null, which would explain why the array was empty. It was a legacy code I had :)

Related

Nuxt store getter not working, ID given to payload is not an Integer + Error: [vuex] do not mutate vuex store state outside mutation handlers

I am trying to make a product detail page. The detail page is named _id.
When opened the id is replaced with the product id. On opening the page the state is set with data fetched from an api.
After that i am trying to use a computed property that refers to a getter named getProduct() with an id (this.$route.params.id) in the payload.
This is how my _id.vue looks like:
methods: {
...mapActions("products", ["fetchProducts",]),
...mapGetters("products", ["getProduct",]),
},
async mounted() {
this.fetchProducts()
},
computed: {
product() {
return this.getProduct(this.$route.params.id)
}
}
This is how my store file named products.js looks like:
import axios from "axios"
export const state = () => ({
producten: []
})
export const mutations = {
setProducts(state, data) {
state.producten = data
}
}
export const getters = {
getProduct(state, id) {
console.log(id)
return state.producten.filter(product => product.id = id)
}
}
export const actions = {
async fetchProducts({ commit }) {
await axios.get('/api/products')
.then(res => {
var data = res.data
commit('setProducts', data)
})
.catch(err => console.log(err));
}
}
What works is creating the state, but when i try to use the getter something goes wrong.
As you can see i console.log() the id given to it. Which logs the following:
I also get the error: client.js?06a0:103 Error: [vuex] do not mutate vuex store state outside mutation handlers.
Which I'm not doing as far as I know?
**Note: **these errors get logged as much as the length of my state array is.
From the Vuex documentation:
Vuex allows us to define "getters" in the store. You can think of them as computed properties for stores. Like computed properties, a getter's result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed.
Like computed, getters does not support having arguments.
But there is a way to have "method-style access" to a getter: https://vuex.vuejs.org/guide/getters.html#property-style-access
You can also pass arguments to getters by returning a function. This is particularly useful when you want to query an array in the store:
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
Note that getters accessed via methods will run each time you call them, and the result is not cached.

Vuex Getter not pulling data

I have a vuex store that I am pulling data from into a component. When the page loads the first time, everything behaves as expected. Yay.
When I refresh the page data is wiped from the store as expected and pulled again into the store as designed. I have verified this is the case monitoring the state using Vuex dev tools. My getter however doesn't pull the data this time into the component. I have tried so many things, read the documentation, etc and I am stuck.
Currently I am thinking it might be an issue with the argument?...
If I change the argument in the getter, 'this.id' to an actual value (leaving the dispatch alone - no changes there), the getter pulls the data from the store. So it seems the prop, this.id has the correct data as the dispatch statement works just fine. So why then wouldn't the getter work?
this.id source - The header includes a search for the person and passes the id of the person that is selected as the id prop. example data: playerId: 60
Thoughts? Appreciate any help.
This code works on initial page load, but not on page refresh.
props: ["id"],
methods: {
fetchStats() {
this.$store.dispatch("player/fetchPlayer", this.id).then(() => {
// alert(this.id);
this.player = this.$store.getters["player/getPlayerById"](this.id);
this.loading = false;
});
}
},
This code (only changing this.id to '6' on getter) works both on initial load and page refresh.
props: ["id"],
methods: {
fetchStats() {
this.$store.dispatch("player/fetchPlayer", this.id).then(() => {
// alert(this.id);
this.player = this.$store.getters["player/getPlayerById"](6);
this.loading = false;
});
}
},
Here is the getPlayerById getter:
getPlayerById: state => id => {
return state.players.find(plr => plr.playerId === id);
},
Here is the fetchPlayer action:
export const actions = {
fetchPlayer({ state, commit, getters }, id) {
// If the player being searched for is already in players array, no other data to get, exit
if (getters.getIndexByPlayerId(id) != -1) {
return;
}
// If the promise is set another request is already getting the data. return the first requests promise and exit
if (state.promise) {
return state.promise;
}
//We need to fetch data on current player
var promise = EventService.getPlayer(id)
.then(response => {
commit("ADD_PLAYER", response.data);
commit("CLEAR_PROMISE", null);
})
.catch(error => {
console.log("There was an error:", error.response);
commit("CLEAR_PROMISE", null);
});
//While data is being async gathered via Axios we set this so that subsequent requests will exit above before trying to fetch data multiple times
commit("SET_PROMISE", promise);
return promise;
}
};
and mutations:
export const mutations = {
ADD_PLAYER(state, player) {
state.players.push(player[0]);
},
SET_PROMISE(state, data) {
state.promise = data;
},
CLEAR_PROMISE(state, data) {
state.promise = data;
}
};

What is the difference between Vuex subscribe and getter?

I have a Product.vue component that displays product information. It updates whenever the ProductID in the route changes with data that is stored in vuex. It is done like this:
setup() {
...
// function to trigger loading product into vuex store state
async function loadProduct() {
try {
await $store.dispatch('loadProduct', {ProductID: $route.params.ProductID})
} catch (error) {
console.log(error);
}
}
// watch the route for changes to the ProductID param and run loadProduct() function above
watch(() => $route.params.ProductID, async () => {
if ($route.params.ProductID) {
loadProduct();
}
},
{
deep: true,
immediate: true
}
)
// Get the product data using a getter
const Product = computed(() => $store.getters.getProduct);
}
When I use the above code and go to a route like localhost:8080/product/123, the value of const Product is empty then after a split second it will have the correct product data. If I then go to another route like localhost:8080/product/545, the value of const Product will be the old product data of 123 before updating to 545. This is probably expected behaviour, but it messes up SSR applications which will return to the browser the old data as HTML.
I then came across vuex subscribe function which solves the problem. But I don't understand why or how it is different to a computed getter. This is the only change required to the code:
setup() {
...
const Product = ref();
$store.subscribe((mutation, state) => {
Product.value = state.productStore.product
})
}
Now the store is always populated with the new product data before the page is rendered and SSR also gets the correct updated data. Why is this working better/differently to a computed property?
computed() is Vue internal, and is updated when any ref being called inside of it is updated.
subscribe() is Vuex specific and will be called whenever any mutation was called.

Make Vue template wait for global object returned by AJAX call

I'm trying to wait for certain strings in a sort of dictionary containing all the text for buttons, sections, labels etc.
I start out by sending a list of default strings to a controller that registers all the strings with my CMS in case those specific values do not already exist. After that I return a new object containing my "dictionaries", but with the correct values for the current language.
I run the call with an event listener that triggers a dispatch() on window.onload, and then add the data to a Vuex module state. I then add it to a computed prop.
computed: {
cartDictionary() {
return this.$store.state.dictionaries.myDictionaries['cart']
}
}
So now here's the problem: In my template i try to get the values from the cartDictionaryprop, which is an array.
<h2 class="checkout-section__header" v-html="cartDictionary['Cart.Heading']"></h2>
But when the component renders, the prop doesn't yet have a value since it's waiting for the AJAX call to finish. And so of course I get a cannot read property of undefined error.
Any ideas on how to work around this? I would like to have the dictionaries accessible through a global object instead of passing everything down through props since it's built using atomic design and it would be insanely tedious.
EDIT:
Adding more code for clarification.
My module:
const dictionaryModule = {
namespaced: true,
state: {
dictionaries: []
},
mutations: {
setDictionaries (state, payload) {
state.dictionaries = payload
}
},
actions: {
getDictionaries ({commit}) {
return new Promise((resolve, reject) => {
Dictionaries.init().then(response => {
commit('setDictionaries', response)
resolve(response)
})
})
}
}
}
My Store:
const store = new Vuex.Store({
modules: {
cart: cartModule,
search: searchModule,
checkout: checkoutModule,
filter: filterModule,
product: productModule,
dictionaries: dictionaryModule
}
})
window.addEventListener('load', function () {
store.dispatch('dictionaries/getDictionaries')
})
I think you can watch cartDictionary and set another data variable.
like this
<h2 class="checkout-section__header" v-html="cartHeading"></h2>
data () {
return {
cartHeading: ''
}
},
watch: {
'cartDictionary': function (after, before) {
if (after) {
this.cartHeading = after
}
}
}
Because this.$store.state.dictionaries.myDictionarie is undefined at the the begining, vuejs can't map myDictionarie['core']. That's why your code is not working.
You can do this also
state: {
dictionaries: {
myDictionaries: {}
}
}
and set the dictionaries key values during resolve.
I also would have liked to see some more of your code, but as i can't comment your questions (you need rep > 50), here it goes...
I have two general suggestions:
Did you setup your action correctly? Mutations are always synchronous while actions allow for asynchronous operations. So, if you http client returns a promise (axios does, for example), you should await the result in your action before calling the respective mutation. See this chapter in the official vuex-docs: https://vuex.vuejs.org/guide/actions.html
You shouldn't be using something like window.onload but use the hooks provided by Vue.js instead. Check this: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
EDIT: As a third suggestion: Check, whether action and mutation are called properly. If they are handled in their own module, you have to register the module to the state.

computed property not reading data initialized in created

I am not sure when computed property (in vue lifecycle) comes. Let's say I have a method that I run in created() as:
created () {
getSomething()
}
Inside getSomething() I fetch data and fill my data property as:
getSomething(){
axios.get(...) { this.info = response.data }
}
Now, in computed properties, I do:
computed: {
doSomething () {
this.info.forEach(item => {})
}
}
But, inside my computed I get forEach is undefined, as if this.info is not an array or has not be filled.
What am I doing wrong? are computed props called before created() or something?
try something like this
getSomething(){
return axios.get(...) { this.info = response.data }
}
then you can use the promise returned in created method like this....
created () {
getSomething().then( () => {
doSomething()
}}
}
You could utilise Vuex' state management...
Vuex has :
Dispatches
Actions
Mutations
Getters
What i am thinking is, on your page load, map an action to the store that makes a call to your axios service and after it returns, commit a mutation that stores the response to the store...
then you can map the state to your component and retrieve the data.
This might not be the quickest answer but if you are expecting to need the response data from the axios request, it might be worth putting it into some sort of state management :-)