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;
}
Related
Created a new service .js called room.module.js
and inside my Vue view I have the following event on form submit:
submit: function(e) {
e.preventDefault();
var name = this.$refs.name.value;
var capacity = this.$refs.places.value;
// dummy delay
setTimeout(() => {
// send update request
this.$store.dispatch(CREATE_ROOM, {
"name": name,
"places": capacity
});
}, 2000);
and my service room.module.js:
// action types
import ApiService from "#/core/services/api.service";
import JwtService from "#/core/services/jwt.service";
export const CREATE_ROOM = "createNewRoom";
// mutation types
export const SET_ROOM_INFO = "setRoomInfo";
const state = {
room_info: {
name: "Room 1",
places: 10,
status: 1
}
};
const getters = {
currentRoomInfo(state) {
return state.room_info;
}
};
const actions = {
[CREATE_ROOM](context, payload) {
if (JwtService.getToken()) {
ApiService.setHeader();
ApiService.put("/room/create", payload).then(({ data }) => {
context.commit(SET_ROOM_INFO, payload);
return data;
});
}
}
};
const mutations = {
[SET_ROOM_INFO](state, room_info) {
state.room_info = room_info;
}
};
export default {
state,
actions,
mutations,
getters
};
but when I submit the form, the following error occurs:
[vuex] unknown action type: createNewRoom
I know I'm missing something, but can't figure out what.
Any ideas? Thank you!
It seems it needs to be added to the Vuex store.
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,
},
},
When payload.key is a key like foo, the code below is working fine, but how to update the value of a child key like foo.bar.a ?
export const mutations = {
USER_UPDATE(state, payload) {
console.log(payload);
state.user = Object.assign({}, state.user, {
[payload.key]: payload.value
});
}
}
=== EDIT ===
This is called by:
computed: {
...mapState(['user']),
fooBarA: {
get() {
return this.$store.state.user.foo.bar.a
},
set(value) {
this.$store.commit('USER_UPDATE', {
key: 'foo.bar.a',
value
})
}
}
}
You are replacing the whole state.user Object reference with a new Object, which destroys reactivity.
This simplified code does not demonstrate the need to use Object.assign, so in this cas you can simply:
export const mutations = {
USER_UPDATE(state, payload) {
state.user[payload.key] = payload.value
}
}
Which keeps the original state.user Object reference.
I have an approach that works.
Where the payload.key is "foo.bar.a"
const mutations = {
UPDATE_USER(state, payload) {
const setter = new Function(
"obj",
"newval",
"obj." + payload.key + " = newval;"
);
let user = { ...state.user };
setter(user, payload.value);
state.user = user;
}
};
Demo
https://codesandbox.io/s/vuex-store-nested-key-setter-x1n00
Inspiration from
https://stackoverflow.com/a/30360979/815507
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.
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.