Cannot access Vuex getter inside method - vue.js

The method below calls a weather API and since it needs to fetch API data before the component loads, it's placed in a created lifecycle hook.
getWeather() {
const lat = this.$store.getters.getLatitude;
const long = this.$store.getters.getLatitude;
console.log('lat is ' + lat);
let url =
'http://api.openweathermap.org/data/2.5/weather?lat=' +
lat +
'&lon=' +
long +
'&units=metric&APPID=' +
process.env.VUE_APP_OPEN_WEATHER_API_KEY;
axios
.get(url)
.then((response) => {
this.currentTemp = response.data.main.temp + '°C';
this.minTemp = response.data.main.temp_min + '°C';
this.maxTemp = response.data.main.temp_max + '°C';
this.pressure = response.data.main.pressure + 'hPa';
this.humidity = response.data.main.humidity + '%';
this.wind = response.data.wind.speed + 'm/s';
})
.catch((error) => {
console.log(error);
});
},
},
created() {
this.getWeather();
},
Vuex store
import Vue from 'vue';
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
latitude: '',
longitude: '',
},
mutations: {
SET_LATITUDE(state, payload) {
state.latitude = payload
},
SET_LONGITUDE(state, payload) {
state.longitude = payload
}
},
actions: {
GET_DATA({ commit }) {
var self = this
.
.
.
// LOGIC TO GET USER DATA ( LAT AND LONG FROM DATABASE )
.
.
.
self.ddb_data = [...data.Items];
const latitude = self.ddb_data[0].user_meta_data.coordinates.latitude
const longitude = self.ddb_data[0].user_meta_data.coordinates.longitude
commit('SET_LATITUDE', latitude)
commit('SET_LONGITUDE', longitude)
}
});
}
});
});
}
} catch (e) {
console.log(e);
return;
}
},
},
},
getters: {
getLatitude(state) {
return state.latitude;
},
getLongitude(state) {
return state.longitude;
},
},
});
export default store;
The issue comes in getting the lat and long from the vuex getters i.e. $store.getters.getLatitude and $store.getters.getLongitude. It seems like the getters are not returning the data when used inside the method (after page is reloaded ). The getters seem to work when used in a computed property ( regardless of page reload ).
How do I access the getters' data inside the method ?

In this case a watch property as follows seems to solve the issue,
watch: {
latLong: {
handler(value) {
if (value.lat && value.long) {
this.getWeather();
}
},
immediate: true,
},
},

Related

How to fetch data with slot in Vue?

I have a code block like this.
<template slot="name" slot-scope="row">{{row.value.first}} {{row.value.last}}</template>
Also I have a header.
{ isActive: true, age: 38, name: { first: 'Jami', last: 'Carney' } },
{ isActive: false, age: 27, name: { first: 'Essie', last: 'Dunlap' } },
{ isActive: true, age: 40, name: { first: 'Thor', last: 'Macdonald' } },
This code is running clearly but I want to show data from my API. Which terms do I need to know? I used Axios before in React. Where can I define Axios method? Do I need to change the template slot instead of :v-slot ?
Although you can make API calls directly from inside the component code, it does not mean that you should. It's better to decouple API calls into a separate module.
Here's a good way to do it which properly follows Separation of Concern (SoC) principle:
Create a directory services under src if it's not already there.
Under services, create new file named api.service.js.
api.service.js
import axios from 'axios';
const baseURL = 'http://localhost:8080/api'; // Change this according to your setup
export default axios.create({
baseURL,
});
Create another file peopleData.service.js
import api from './api.service';
import handleError from './errorHandler.service'; // all the error handling code will be in this file, you can replace it with console.log statement for now.
export default {
fetchPeopleData() {
return api.get('/people')
.catch((err) => handleError(err));
},
// All other API calls related to your people's (users'/customers'/whatever is appropriate in your case) data should be added here.
addPerson(data) {
return api.post('/people', data)
.catch((err) => handleError(err));
},
}
Now you can import this service into your component and call the function.
<template>
... Template code
</template>
<script>
import peopleDataService from '#/services/peopleData.service';
export default {
data() {
return {
rows: [],
};
},
mounted() {
peopleDataService.fetchPeopleData().then((res) => {
if (res && res.status == 200) {
this.rows = res.data;
}
});
},
}
</script>
You haven't given us any idea about your current setup. If you're using Vue-Router, it's better to fetch data in navigation guards, especially if your component is relying on the data: Data Fetching
Simply shift the code from mounted() into a navigation guard. this may not be available, so you will have to use next callback to set rows array, it's explained in the link above.
You can use Axios in methods or mounted.
mounted(){
this.loading = true;
axios
.get(`${this.backendURL}/api/v1/pages/layouts` , authHeader())
.then(response => (this.layouts = response.data.data))
.catch(handleAxiosError);
}
methods: {
/**
* Search the table data with search input
*/
uncheckSelectAll(){
this.selectedAll = false
},
onFiltered(filteredItems) {
// Trigger pagination to update the number of buttons/pages due to filtering
this.totalRows = filteredItems.length;
this.currentPage = 1;
},
handlePageChange(value) {
this.currentPage = value;
axios
.get(`${this.backendURL}/api/v1/pages?per_page=${this.perPage}&page=${this.currentPage}` , authHeader())
.then(response => (this.pagesData = convert(response.data.data),
this.pagesDataLength = response.data.pagination.total));
},
handlePerPageChange(value) {
this.perPage = value;
this.currentPage = 1;
axios
.get(`${this.backendURL}/api/v1/pages?per_page=${this.perPage}&page=${this.currentPage}` , authHeader())
.then(response => (this.pagesData = convert(response.data.data),
this.pagesDataLength = response.data.pagination.total));
},
deletePage(){
this.loading = true
this.$bvModal.hide("modal-delete-page");
window.console.log(this.pageIdentity);
if (!roleService.hasDeletePermission(this.pageIdentity)){
return;
}
axios
.delete(`${this.backendURL}/api/v1/pages/${this.page.id}` , authHeader())
.then(response => (
this.data = response.data.data.id,
axios
.get(`${this.backendURL}/api/v1/pages?per_page=${this.perPage}&page=${this.currentPage}` , authHeader())
.then(response => (this.pagesData = convert(response.data.data),
this.pagesDataLength =
response.data.pagination.total)),
alertBox(`Page deleted succesfully!`, true)
))
.catch(handleAxiosError)
.finally(() => {
this.loading = false
});
}

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.

Cannot pass multiple arguments in vuex actions

I'm trying to call vuex action in vue component with multiple parameters. But in action method cannot access these passed arguments.
I have already tried passing value in payload as object which is mostly suggested here. but still it is not working.
Please look for
this.getMessageFromServer(payload);
MessageBox.vue
import Vue from 'vue';
import { mapGetters, mapActions } from 'vuex';
import MessageView from './MessageView.vue';
export default Vue.component('message-box',{
components:{
MessageView
},
data() {
return {
messageList :[],
}
},
created() {
this.fetchTimeMessage();
console.log("reaching inside ");
},
computed:{
...mapGetters(['getMessage','getActiveMessageData']),
...mapActions(['getMessageFromServer']),
},
methods: {
fetchTimeMessage:function(){
console.log("fetchTimeMessage : ");
var messageUser = this.getMessage.findIndex((e) => e.muid == this.getActiveMessageData.id);
console.log("fetchTimeMessage : " , {messageUser});
if (messageUser == -1) {
let user_id = this.getActiveMessageData.id;
let user_type = this.getActiveMessageData.type;
console.log("inside fetch Message : " + user_id);
console.log("inside fetch Message : " + user_type);
const payload = {
'uType': user_type,
'uid' : user_id,
'limit': 50
};
this.getMessageFromServer(payload);
}
},
},
});
Vuex modules message.js
const state = {
messages:[],
activeMessage : {}
};
const getters = {
getActiveUserId: (state) => {
let activeUserId = "";
if (!utils.isEmpty(state.activeMessage)) {
activeUserId = state.activeMessage.id;
}
return activeUserId;
},
getActiveMessage:(state) => { return !utils.isEmpty(state.activeMessage);},
getActiveMessageData : (state) => {return state.activeMessage } ,
getMessage: (state) => {return state.messages},
};
const actions = {
getMessageFromServer({ commit, state },{utype,uid,limit}){
console.log("mesage callback asdas : " + uid);
let messageRequest = CCManager.messageRequestBuilder(utype, uid, limit);
messageRequest.fetchPrevious().then(messages => {
//console.log("mesage callback : " + JSON.stringify(messages));
// handle list of messages received
let payload = {
'messsages':messages,
'id': uid
};
console.log("inside action_view : " + JSON.stringify(payload));
//commit('updateMessageList',payload);
})
},
setActiveMessages:function({commit},data){
commit('updateActiveMessage',data);
},
};
const mutations = {
updateMessageList(state,{messages,id}){
console.log("action details" + id);
//uid is not present
var tempObj = {
'muid' : id,
'message' : messages
}
state.messages.push(tempObj);
}
},
updateActiveMessage(state,action){
state.activeMessage = {
type: action.type,
id: action.uid
};
}
};
export default {
state,
getters,
actions,
mutations
};
Change the way you call the action in your component:
this.$store.dispatch('getMessageFromServer', payload);
And pass the payload as a single object in your action function:
getMessageFromServer({ commit, state }, payload)
And you can then access the payload properties in the action like this:
getMessageFromServer({ commit, state }, payload) {
var uid = payload.uid;
var uType = payload.uType;
var limit = payload.limit;
}

Vuex - Baffling mutation payload in new window/tab

I have a simple VueJS SPA served by Express. Express also handles API endpoints called by Vue front-end.
Express is connected to Postgres, and API endpoints interact with the database (perform basic CRUD operations).
In my database, I have a single "patient" table, with columns "first_name", "last_name", "date_of_birth", and "id".
In the created() hook of PatientList.vue component, database is queried for all patients, and this information is saved to component data, displayed using v-for loop.
My PatientList.vue code is:
<script>
import auth from '#/auth/authenticator';
import { mapMutations } from 'vuex';
export default {
components: {
name: 'PatientsList',
},
data() {
return {
patients: [],
}
},
computed: {
accessTokenGetter: {
get: function () {
return this.$store.getters.accessToken;
},
},
patientEditStatusGetter: {
get: function () {
return this.$store.getters.g_patientEditStatusCheck;
},
},
},
methods: {
...mapMutations([
'm_startPatientEditProcess',
'm_endPatientEditProcess',
'm_clearPatientEditState',
'm_cachePatient'
]),
cachePatientHandler(ptnt) {
console.log('PatientList.vue method cachePatientHandler', ptnt);
var patientObject = {
'date_of_birth': ptnt.date_of_birth.split('T')[0],
'first_name': ptnt.first_name,
'last_name': ptnt.last_name,
'patient': ptnt.patient,
'uid': ptnt.uid
}
this.m_endPatientEditProcess(false);
this.m_clearPatientEditState('');
this.m_startPatientEditProcess(true);
this.m_cachePatient(patientObject);
},
getPatients() {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://voyager.wrk.health/patients/index');
xhr.setRequestHeader('Authorization', `Bearer ${this.accessTokenGetter}`);
xhr.setRequestHeader('Cache-control', 'no-cache');
xhr.onload = () => {
var data = JSON.parse(xhr.response);
for( var i=0, r = data.results; i<r.length; i++ ){
this.patients.push(r[i]);
}
};
xhr.onerror = () => {
console.log(xhr.statusText);
};
xhr.send();
},
},
beforeCreate() {
},
created() {
console.log('PatientList.vue created()');
if(auth.isUserLogged()){
this.getPatients();
} else {
router.go('/');
}
},
};
</script>
In order to edit a patient, I have router-link to edit page. Router-link has click-handler, argument passed in is iterable from v-for loop (i.e. single patient object). I have 4 mutations related to this
const mutations = {
m_startPatientEditProcess(state, trueStatus) {
console.log('Vuex patient m_startPatientEditProcess');
state.patientEditStatus = trueStatus;
},
m_endPatientEditProcess(state, falseStatus) {
console.log('Vuex patient m_endPatientEditProcess');
state.patientEditStatus = falseStatus;
},
m_clearPatientEditState(state, emptyString) {
console.log('Vuex patient m_clearPatientEditState');
state.patientDetails.date_of_birth = emptyString;
state.patientDetails.first_name = emptyString;
state.patientDetails.last_name = emptyString;
state.patientDetails.patient = emptyString;
state.patientDetails.uid = emptyString;
},
m_cachePatient(state, patientObj) {
console.log('Vuex patient m_cachePatient, received: ', patientObj);
state.patientDetails.date_of_birth = patientObj.date_of_birth;
state.patientDetails.first_name = patientObj.first_name;
state.patientDetails.last_name = patientObj.last_name;
state.patientDetails.patient = patientObj.patient;
state.patientDetails.uid = patientObj.uid;
},
Also, my PatientEdit.vue code is:
<script>
import { mapMutations } from 'vuex';
export default {
components: {
name: 'PatientEdit',
},
data() {
return {
patientToEdit: {
first_name: '',
last_name: '',
date_of_birth: '',
patient: '',
uid: '',
},
patientDetailsLoaded: false,
}
},
computed: {
patientToEditDetailsGetter: {
get: function() {
return this.$store.getters.g_patientToEditDetails;
}
},
accessTokenGetter: {
get: function() {
return this.$store.getters.accessToken;
}
}
},
methods: {
...mapMutations([
'm_endPatientEditProcess',
'm_clearPatientEditState',
]),
populatePatientEditState() {
const pDeets = this.patientToEditDetailsGetter;
this.patientToEdit.first_name = pDeets.first_name;
this.patientToEdit.last_name = pDeets.last_name;
this.patientToEdit.date_of_birth = pDeets.date_of_birth;
this.patientToEdit.patient = pDeets.patient;
this.patientToEdit.uid = pDeets.uid;
this.patientDetailsLoaded = true;
},
submitUpdatedPatientDetails() {
const payload = Object.assign({}, this.patientToEdit);
const xhr = new XMLHttpRequest();
xhr.open('PUT', `https://voyager.wrk.health/patients/update/${payload.uid}`)
xhr.setRequestHeader('Content-type', 'application/json');
xhr.setRequestHeader('Authorization', `Bearer ${this.accessTokenGetter}`);
xhr.onload = async () => {
try {
await console.log(xhr.response);
await console.log('Sent patient data to update endpoint \n Ready to be redirected.');
await Promise.all([this.m_endPatientEditProcess(false), this.m_clearPatientEditState('')]);
await this.$router.push('/patients/index');
} catch (e) {
throw new Error(e);
}
}
xhr.send(JSON.stringify(payload));
}
},
created() {
this.populatePatientEditState();
},
};
</script>
My reasoning was to avoid unnecessary request to database.
Everything works as intended. I have a store.subscription set up to save Vuex state to localStorage (for session persistence when this application is refreshed).
Store subscription logs state and mutation, everything is normal like so:
First store output
If I open a new tab or window (cookies left untouched), and try to perform the same update operations, my store subscription freaks out, and I cannot auto-populate my PatientEdit page with patient information from Vuex.
According to the output, suddenly mutation is committing things that I never specified like so:
Store output 2
Why does this happen?
Thanks for reading.
NB: If I have missed information necessary to figure this behaviour out, please let me know.
Edit 1:
Vuex store:
import Vue from 'vue';
import Vuex from 'vuex';
import session from './modules/session';
import patient from './modules/patient';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
session,
patient,
},
mutations: {
initStore(state) {
console.log('Vuex root state checking for local snapshot');
if (localStorage.getItem('store')) {
console.log('Snapshot found, hydrating...');
this.replaceState(Object.assign(store, JSON.parse(localStorage.getItem('store'))));
}
},
},
});
store.commit('initStore');
store.subscribe((mutation, state) => {
console.warn('Subscription detected');
console.log('mutation: ', mutation);
console.log('state: ', state);
localStorage.setItem('store', JSON.stringify(state));
});
export default store;
You end up with a "cannot stringify circular JSON" error, because you are turning the state, but also the getters, mutations and actions into a string. These contain references to the object you are trying to stringify, which results in an infinite loop.
This is not a problem in your first run, because your localStorage is still empty then. You correctly stringify your state, but when you reload the following line runs:
this.replaceState(Object.assign(store, JSON.parse(localStorage.getItem('store'))));
This line replaces your state with your store, extended with what you have in localStorage. If you replace store with state things should work much better.

Vuex - Passing a state's object into another state's object

In Vuex I'm trying to pass a state's object (a string in this case), into another state's object, but it is returning undefined.
state: {
notifications: [
{ key: "success",
notification: "Awesome " + this.theName + "! Success.",
redirectPath: "/home"
},
{ key: "error",
notification: "Oh no " + this.theName + "... Error.",
redirectPath: "/error"
}
],
theName: 'Ricky Bobby' // this would normally come from a mutation method - see below
}
The example above the theName is hard-coded just for testing but its value is coming from a mutation method. I know it is coming in into the store's state, because I am able to console log it. But the string interpolation inside the notifications object is not working. How can I pass that incoming value into the notifications.notification value?
I don't know if this helps, but here is the mutation example:
mutations: {
loginSuccess(state, payload){
state.theName = payload.uName;
}
}
There're two issues with your code. Firstly, this doesn't work the way you're trying to make it to do. In your question this inside each notification doesn't refer to the state or any other part of your code. Its value is the global window object or undefined, depends on whether you are in strict mode:
const object = {
propName: this,
};
console.log(object.propName);
Secondly, you code is asynchronous, so theName would change from time to time, but you never actually redefine message strings in your notifications. And they won't be 'recalculated' by itself:
let surname = 'Whyte';
const object = {
fullName: 'Pepe ' + surname,
};
console.log(object.fullName);
setTimeout(() => {
surname = 'White';
console.log(object.fullName);
console.log('the value of \'surname\' variable is ' + surname + ' though.');
}, 2000);
What you can do in your case is to define notification as a function:
notification(name) { return "Awesome " + name + "! Success."}
Then write a getter for notifications and pass a name to the function.
Or as an alternative you can refer to the object itself inside the function. Like this:
let surname = 'Whyte';
const object = {
person: {
firstName: 'Pepe ',
fullName: () => {
return object.person.firstName + ' ' + surname;
},
}
};
console.log(object.person.fullName());
setTimeout(() => {
object.person.firstName = 'Keke';
console.log(object.person.fullName());
}, 1000);
UPD: I've made another example for you. It's hard to tell how exactly you are going to call this notifications, but here are two options you can access them the way you want (jsfiddle):
const store = new Vuex.Store({
state: {
theName: 'Ricky Bobby',
// accessing `theName` prop inside state (probably won't be possible in the real project or very inconvinient due to modularity)
successNotificationInState: () => `Awesome ${store.state.theName}! Success.`,
},
// accessing the same prop with getter
getters: {
successNotification: (state) => `Awesome ${state.theName}! Success.`,
},
mutations: {
loginSuccess(state, payload) {
state.theName = payload.uName;
},
},
actions: { // let's emulate a login
login({
commit
}) {
return new Promise(fullfil => {
setTimeout(function() {
console.log('logging in')
const response = {
uName: 'Keke',
email: 'keke#gmail.com',
}
fullfil(response);
commit('loginSuccess', response);
}, 2000);
});
},
},
});
const app = new Vue({
el: "#app",
store,
data: {
msgGetter: '',
msgState: '',
},
computed: {},
methods: {
login() {
this.$store.dispatch('login').then((response) => {
console.log(response);
console.log(this.$store);
this.msgGetter = this.$store.getters.successNotification;
this.msgState = this.$store.state.successNotificationInState();
});
},
},
mounted() {
this.login();
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://unpkg.com/vue"></script>
<div id="app">
<p>Message from state: {{msgState}}</p>
<p>Message from getter: {{msgGetter}}</p>
</div>