How to get the cookie value and put it into the Vuex store after refreshing page in vue - api

I have a product component and I have an I am adding products into cart there:
addToCart: function () {
this.amount = this.itemsCount !== "" ? this.itemsCount : 1;
if(this.variationId != null) {
this.warningMessage = false;
cartHelper.addToCart(this.product.id, this.variationId, parseInt(this.amount), (response) => {
this.$store.dispatch('addProductToCart', {
cart: response.data,
})
});
} else {
this.warningMessage = true;
}
},
And I also have cart helper where I am making my API calls and store the cart_guid in the cookie:
let cartHelper = {
cartCookieName: "_cart",
cookieValue: "",
getCart: function (callback = undefined) {
return apiHelper.getRequest(
"/carts",
(response) => {
document.cookie = `${this.cartCookieName}=${response.data.attributes.cart_guid};`;
this.cookieValue = response.data.attributes.cart_guid;
if (callback) { callback(response); }
}
)
},
addToCart: function (product, variation_id, amount, callback = undefined) {
if(this.cookieValue == "") {
this.getCart(() => {
this._addToCart(product, variation_id, amount, callback);
});
} else {
this._addToCart(product, variation_id, amount, callback)
}
},
_addToCart(product, variation_id, amount, callback = undefined) {
return apiHelper.postRequest(
`/carts/${this.cookieValue}/add-item`,
(response) => {
document.cookie = `${this.cartCookieName}=${response.data.attributes.cart_guid};`;
if (callback) { callback(response); }
},
{
product_id: product,
variation_id: variation_id,
amount: amount,
}
)
},
export default cartHelper;
(I didnt write the code where I am storing the cart_guid in the cookie. I dont think it is necessary, it is basically cookieValue)
So when I add the product into the cart, I am storing this data in Vuex. For this my action:
export const addProductToCart = ({commit}, {cart}) => {
commit('ADD_TO_CART', {cart});
}
my mutation:
export const ADD_TO_CART = (state, {cart}) => {
state.cart = cart;
}
and my state:
export default {
cart: {
"attributes": {
"items": [],
}
}
}
What I am trying to do when I refresh the page, the values in Vuex are lost but since there is still a cookie with the value cart_guid, I should basically make this call and fill the Vuex again with the cart_guid. But I am quite new in Vuex, so I don't know where I should put the logic. I would be really glad if you give me any hint or code.

There is a onMounted lifecycle where the code inside will run whenever the vue component has been successfully mounted onto the DOM. You can put your function where it retrieves the value of your cookie in there so it will after mounted.

Related

Vuejs created and mounted doesn't work properly even if at the same level than methods

I'm experiencing a strange behaviour with created() and mounted() in Vue.js. I need to set 2 lists in created() - so it means those 2 lists will help me to create a third list which is a merge.
Here is the code :
// return data
created () {
this.retrieveSellOffers();
this.getAllProducts();
},
mounted () {
this.mergeSellOffersProducts();
},
methods: {
retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
SellOfferServices.getAllBySellerId(this.sellerId)
.then((response) => {
this.sellOffers = response.data;
console.log("this.sellOffers");
console.log(this.sellOffers);
})
.catch((e) => {
console.log(e);
});
},
getAllProducts() {
ProductServices.getAll()
.then((response) => {
this.products = response.data;
console.log("this.products");
console.log(this.products);
})
.catch((e) => {
console.log(e);
});
},
mergeSellOffersProducts () {
console.log(this.products) // print empty array
console.log(this.sellOffers) // print empty array
for (var i = 0; i < this.sellOffers.length; i++) {
if (this.sellOffers[i].productId === this.products[i]._id) {
this.arr3.push({id: this.sellOffers[i]._id, price: this.sellOffers[i].price, description: this.products[i].description});
}
}
this.arr3 = this.sellOffers;
},
}
//end of code
So my problem is when I enter in mergeSellOffersProducts(), my 2 lists are empty arrays :/
EDIT :
This way worked for me :
async mounted() {
await this.retrieveSellOffers();
await this.getAllProducts();
this.mergeSellOffersProducts();
},
methods: {
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('link/api/selloffer/seller/', { params: { sellerId: this.sellerId } })).data;
},
async getAllProducts() {
this.products = (await axios.get('link/api/product')).data;
},
}
I think the reason is: Vue does not wait for the promises to resolve before continuing with the component lifecycle.
Your functions retrieveSellOffers() and getAllProducts() contain Promise so maybe you have to await them in the created() hook:
async created: {
await this.retrieveSellOffers();
await this.getAllProducts();
}
So I tried to async my 2 methods :
async retrieveSellOffers() {
this.sellerId = localStorage.sellerId;
this.sellOffers = (await axios.get('linkhidden/api/selloffer/', { params: { sellerId: '615b1575fde0190ad80c3410' } })).data;
console.log("this.sellOffers")
console.log(this.sellOffers)
},
async getAllProducts() {
this.products = (await axios.get('linkhidden/api/product')).data;
console.log("this.products")
console.log(this.products)
},
mergeSellOffersProducts () {
console.log("here")
console.log(this.sellOffers)
console.log(this.products)
this.arr3 = this.sellOffers;
},
My data are well retrieved, but yet when I enter in created, the two lists are empty...
You are calling a bunch of asynchronous methods and don't properly wait for them to finish, that's why your data is not set in mounted. Since Vue does not await its lifecycle hooks, you have to deal with the synchronization yourself.
One Vue-ish way to fix it be to replace your method mergeSellOffersProducts with a computed prop (eg mergedSellOffersProducts). Instead of generating arr3 it would simply return the merged array. It will be automatically updated when products or sellOffers is changed. You would simply use mergedSellOffersProducts in your template, instead of your current arr3.
If you only want to update the merged list when both API calls have completed, you can either manually sync them with Promise.all, or you could handle this case in the computed prop and return [] if either of the arrays is not set yet.
When you're trying to merge the 2 lists, they aren't filled up yet. You need to await the calls.
async created () {
await this.retrieveSellOffers();
await this.getAllProducts();
},
async mounted () {
await this.mergeSellOffersProducts();
},

vue apollo 2 composition api track result

So I am trying to add product impressions to my site by following this article:
https://developers.google.com/tag-manager/enhanced-ecommerce#product-impressions
I have created a bit of logic to fire off the required data like this:
import { getCurrentInstance } from "#vue/composition-api";
import { useGtm } from "#gtm-support/vue2-gtm";
export function useTrackProductImpressions(items: any[]) {
console.log("trying to track products", items);
if (!items?.length) return;
const gtm = useGtm();
if (!gtm.enabled()) return;
const dataLayer = window.dataLayer;
if (!dataLayer) return;
console.log(items);
const products = items.map((product, i) => {
const retailers = product.retailers ?? [];
return {
name: product.title, // Name or ID is required.
id: product.id,
price: retailers[0].price,
brand: product.brand,
category: product.categorySlug,
variant: product.variant,
position: i,
};
});
const instance = getCurrentInstance();
const route = instance.proxy.$route;
const routeName = route.meta?.title ?? route.name;
dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object.
dataLayer.push({
event: "productClick",
ecommerce: {
click: {
actionField: { list: routeName }, // Optional list property.
products,
},
},
// eventCallback: function () {
// document.location = productObj.url;
// },
});
}
This seems pretty normal and I have a click version of this that works fine.
The problem is, the click event can be fired when a link is clicked, this one needs to fire when the view loads, I assume in setup.
So, I have my apollo logic:
import { useQuery, useResult } from "#vue/apollo-composable";
import * as listProducts from "#graphql/api/query.products.gql";
export const defaultParameters: {
identifier?: string;
searchTerm: string;
itemsToShow: number;
page: number;
filters: any;
facets: string[];
} = {
searchTerm: "*",
itemsToShow: 12,
page: 1,
filters: [],
facets: ["Criteria/Attribute,count:100"],
};
export function useSearchProducts(params) {
const { result, loading, error, fetchMore } = useQuery(listProducts, params);
const response = useResult(result, null, (data) => data.searchProducts);
return { response, loading, error, fetchMore };
}
And from my setup I invoke like this:
const { category } = toRefs(props);
const page = ref(1);
const skip = ref(0);
const orderBy = ref([
{
key: "InVenue",
value: "desc",
},
]);
const params = computed(() => {
const filters = createFilters("CategorySlug", [category.value.slug]);
const request = createRequest(
defaultParameters,
page.value,
filters,
orderBy.value
);
return { search: request };
});
const { response, loading, error} = useSearchProducts(params);
Which I can then return to the template like this:
return { response, loading, error };
Now I have done this, I want to add some tracking, so initially I did this:
watch(response, (result) => useTrackProductImpressions(result?.value?.items));
But it was always undefined.
I added console log on result within the watch method and it is always undefined.
So I changed to this:
const track = computed(() => {
useTrackProductImpressions(response.value.items);
});
But this never gets invoked (I assume because it has no return value and I don't use it in the template).
My question is, which is the best way to do what I am attempting? Am I missing something or am I on the write track?
I think I was close, I just used the computed property to return my products like this:
const products = computed(() => {
if (!response.value) return [];
useTrackProductImpressions(response.value.items);
return response.value.items;
});
const total = computed(() => {
if (!response.value) return 0;
return response.value.total;
});
const hasMoreResults = computed(() => {
if (!response.value) return false;
return response.value.hasMoreResults;
});
return {
products,
loading,
error,
total,
hasMoreResults,
skip,
more,
search,
};

VueJS $set not making new property in array of objects reactive

In my VueJS 2 component below, I can add the imgdata property to each question in the area.questions array. It works - I can see from the console.log that there are questions where imgdata has a value. But despite using $set it still isn't reactive, and the imgdata isn't there in the view! How can I make this reactive?
var componentOptions = {
props: ['area'],
data: function() {
return {
qIndex: 0,
};
},
mounted: function() {
var that = this;
that.init();
},
methods: {
init: function() {
var that = this;
if (that.area.questions.length > 0) {
that.area.questions.forEach(function(q) {
Util.HTTP('GET', '/api/v1/photos/' + q.id + '/qimage').then(function(response) {
var thisIndex = (that.area.questions.findIndex(entry => entry.id === q.id));
var thisQuestion = (that.area.questions.find(entry => entry.id === q.id));
thisQuestion.imgdata = response.data;
that.$set(that.area.questions, thisIndex, thisQuestion);
})
});
}
console.log("area.questions", that.area.questions);
},
Since area is a prop, you should not be attempting to make changes to it within this component.
The general idea is to emit an event for the parent component to listen to in order to update the data passed in.
For example
export default {
name: "ImageLoader",
props: {
area: Object
},
data: () => ({ qIndex: 0 }), // are you actually using this?
mounted () {
this.init()
},
methods: {
async init () {
const questions = await Promise.all(this.area.questions.map(async q => {
const res = await Util.HTTP("GET", `/api/v1/photos/${encodeURIComponent(q.id)}/qimage`)
return {
...q,
imgdata: res.data
}
}))
this.$emit("loaded", questions)
}
}
}
And in the parent
<image-loader :area="area" #loaded="updateAreaQuestions"/>
export default {
data: () => ({
area: {
questions: [/* questions go here */]
}
}),
methods: {
updateAreaQuestions(questions) {
this.area.questions = questions
}
}
}
Here that variable has a value of this but it's bound under the scope of function. So, you can create reactive property in data as below :
data: function() {
return {
qIndex: 0,
questions: []
};
}
Props can't be reactive so use :
that.$set(this.questions, thisIndex, thisQuestion);
And assign your API output to directly questions using this.questions.

Computed Getter causes maximum stack size error

I'm trying to implement the following logic in Nuxt:
Ask user for an ID.
Retrieve a URL that is associated with that ID from an external API
Store the ID/URL (an appointment) in Vuex
Display to the user the rendered URL for their entered ID in an iFrame (retrieved from the Vuex store)
The issue I'm currently stuck with is that the getUrl getter method in the store is called repeatedly until the maximum call stack is exceeded and I can't work out why. It's only called from the computed function in the page, so this implies that the computed function is also being called repeatedly but, again, I can't figure out why.
In my Vuex store index.js I have:
export const state = () => ({
appointments: {}
})
export const mutations = {
SET_APPT: (state, appointment) => {
state.appointments[appointment.id] = appointment.url
}
}
export const actions = {
async setAppointment ({ commit, state }, id) {
try {
let result = await axios.get('https://externalAPI/' + id, {
method: 'GET',
protocol: 'http'
})
return commit('SET_APPT', result.data)
} catch (err) {
console.error(err)
}
}
}
export const getters = {
getUrl: (state, param) => {
return state.appointments[param]
}
}
In my page component I have:
<template>
<div>
<section class="container">
<iframe :src="url"></iframe>
</section>
</div>
</template>
<script>
export default {
computed: {
url: function (){
let url = this.$store.getters['getUrl'](this.$route.params.id)
return url;
}
}
</script>
The setAppointments action is called from a separate component in the page that asks the user for the ID via an onSubmit method:
data() {
return {
appointment: this.appointment ? { ...this.appointment } : {
id: '',
url: '',
},
error: false
}
},
methods: {
onSubmit() {
if(!this.appointment.id){
this.error = true;
}
else{
this.error = false;
this.$store.dispatch("setAppointment", this.appointment.id);
this.$router.push("/search/"+this.appointment.id);
}
}
I'm not 100% sure what was causing the multiple calls. However, as advised in the comments, I've now implemented a selectedAppointment object that I keep up-to-date
I've also created a separate mutation for updating the selectedAppointment object as the user requests different URLs so, if a URL has already been retrieved, I can use this mutation to just switch the selected one.
SET_APPT: (state, appointment) => {
state.appointments = state.appointments ? state.appointments : {}
state.selectedAppointment = appointment.url
state.appointments = { ...state.appointments, [appointment.appointmentNumber]: appointment.url }
},
SET_SELECTED_APPT: (state, appointment) => {
state.selectedAppointment = appointment.url
}
Then the getUrl getter (changed its name to just url) simply looks like:
export const getters = {
url: (state) => {
return state.selectedAppointment
}
}
Thanks for your help guys.

"Maximum call stack size exceeded" by passing the data to the Vuex-state

I am fetching the data from a MongoDB through sending GET requests to my API. Then I loop through the response.data and in each response.data through its properties to push the data which I need to nextArray. And this nextArray should be passed to the schedulingQuality-state in the Vuex. That's how it looks like:
methods: {
...mapActions(
['setSchedulingQuality']
),
get_data() {
const nextArray = [];
for(let i in this.SelectedtValues) {
axios.get('http://127.0.0.1:5000/getexp/'+this.SelectedtValues[i])
.then(res => {
for(let n in res.data) {
nextArray.push(res.data[n].output)
}
}
)}
console.log(nextArray);
},
computed: {
...mapGetters(
['schedulingQuality','selectedValues']
),
SelectedtValues() {
return this.$store.getters.selectedValues;
} ,
schedulingQuality() {
return this.schedulingQuality;
}
}
When I'm printing out the nextArray then it seems to be ok. I'm getting a [] on the console and after I click on it the correct content appears with a small i icon which tells: "Value below was evaluated just now". However I am not able to print out the items of this Array separately, each of them has a value of undefined, when I try that.
But my main problem is that it throws an Maximum call stack size exceeded error, when I'm trying to pass it to my Vuex-state in the code above befor printing out, like:
this.setSchedulingQuality(nextArray)
Here is my Vuex-code:
import Vuex from "vuex";
import axios from "axios";
const createStore = () => {
return new Vuex.Store({
state: {
schedulingQuality: [],
},
mutations: {
SchedulingQuality(state, payload) {
state.schedulingQuality = payload;
}
},
actions: {
setSchedulingQuality({commit}, payload){
commit('SchedulingQuality',payload)
}
},
getters: {
schedulingQuality(state) {
return state.schedulingQuality;
}
}
});
};
export default createStore;
My questions are:
Why it is not possible to print out the Array items separately?
Why I'am getting this error
And how can I fix it?
Thank you for your time.
axios call is asynchronous. At the time you call console.log(nextArray), axios function is not finished yet. That's why you got empty array.
You call multiple api asynchronously, I suggest you check out Promise.all
get_data() {
const nextArray = [];
Promise.all(this.SelectedtValues.map(value => {
return axios.get('http://127.0.0.1:5000/getexp/' + value)
})).then(results => {
results.map(res => {
for(let n in res.data) {
nextArray.push(res.data[n].output)
}
})
console.log(nextArray);
}).catch(err => {
console.error(err)
})
}