vuex 3.6 why is my state object undefined when using it inside a method - vue.js

I am building an application using vue 2.6.11 and vuex 3.6.0
The page I am building is for an event registration. The ActiveEvent is fetched from the database (Event ID, Date, Location etc) using an API
The registration form first asks for an email address. On blur of the email field we then fire the checkEmail(). This should do one or two API calls. The first call checks to see if we have the email address in the database and returns the ParticipantID, and if we do then a second call is made to see if the participant is already registered against this event using Event.EventID and ActiveUser.ParticipantID
The stucture of the page being loaded is a page component <EventDetail> called from the router. This has a main component <EventRegistration> which calls two separate sub-components: <EventRegistrationBlurb> which gets the state.ActiveEvent passed as a prop and <EventRegistrationForm> which is fetching the state.ActiveEvent directly. The outer component <EventRegistration> is responsible for fetching the Event data from the API and setting state.ActiveEvent which is does successfully,
What I am failing to understand is why when I call checkEmail in my component, this.ActiveEvent is undefined. The puter component is fetching the API and setting the state correctly as the blurb component is correctly rendering it. If I put the ActiveEvent object into the template for the EventRegistrationForm it renders correctly, it is just not being set in time for the binding to be made to the method checkEmail()
I have the following code in my sub-component <EventRegistrationForm>: (NOTE, ActiveEvent is set by an outer component and does get set correctly)
methods: {
...mapActions(['CheckParticipantByEmail']),
async checkEmail () {
const payload = {
email: this.form.email,
EventID: this.ActiveEvent.EventID // <-- THIS IS UNDEFINED???
}
await this.CheckParticipantByEmail(payload)
}
},
computed: {
...mapState(['ActiveEvent', 'ActiveUser'])
}
and then in my store:
state: {
ActiveEvent: {},
ActiveUser: {}
},
mutations: {
SET_ACTIVE_EVENT (state, payload) {
state.ActiveEvent = payload
},
CHECK_PARTICIPANT_BY_EMAIL (state, payload) {
state.ActiveUser = payload
},
GET_PARTICIPANT_FOR_EVENT (state, payload) {
state.ActiveUser = payload
}
},
actions: {
async CheckParticipantByEmail ({ commit }, payload) {
console.log('payload', payload)
const baseUrl = process.env.VUE_APP_API_URL
const url = `${baseUrl}getParticipantbyEmail`
const { email, EventID } = payload
const response = await axios.post(
url,
{
EmailAddress: email
}
)
const User = await response.data[0]
commit('CHECK_PARTICIPANT_BY_EMAIL', User)
if (User.ParticipantID > 0) {
const baseUrl = process.env.VUE_APP_API_URL
const url2 = `${baseUrl}getParticipantForEvent`
const payload2 = {
ParticipantID: User.ParticipantID,
EventID: EventID
}
alert('URL2: ' + url2)
alert('payload2 participant: ' + payload2.ParticipantID)
alert('payload2 event: ' + payload2.EventID)
const response2 = await axios.post(
url2,
payload2
)
// console.log('response: ', response.data[0])
const payload3 = response2.data[0]
commit('GET_PARTICIPANT_FOR_EVENT', payload3)
}
}
}

As usual, it turns out to be an interface error between the chair and the keyboard. This page is normally accessed from a list of events which is an array of objects where the identifier is EventID. When calling the separate events the identifier is just ID so the code in the payload2 should read
const payload2 = {
ParticipantID: User.ParticipantID,
EventID: ID // <- NOTE change of identifier.
}
I think I will update the API to return a consistent identifier and avoid the headache later on. Only wasted about 3 hours on this...

Related

Vue3 / Vuex State is empty when dispatching action inside of lifecycle hook inside of test

We're using the composition API with Vue 3.
We have a Vuex store that, amongst other things, stores the currentUser.
The currentUser can be null or an object { id: 'user-uuid' }.
We're using Vue Test Utils, and they've documented how to use the store inside of tests when using the Composition API. We're using the store without an injection key, and so they document to do it like so:
import { createStore } from 'vuex'
const store = createStore({
// ...
})
const wrapper = mount(App, {
global: {
provide: {
store: store
},
},
})
I have a component and before it is mounted I want to check if I have an access token and no user currently in the store.
If this is the case, we want to fetch the current user (which is an action).
This looks like so:
setup() {
const tokenService = new TokenService();
const store = useStore();
onBeforeMount(async () => {
if (tokenService.getAccessToken() && !store.state.currentUser) {
await store.dispatch(FETCH_CURRENT_USER);
console.log('User: ', store.state.currentUser);
}
});
}
I then have a test for this that looks like this:
it('should fetch the current user if there is an access token and user does not exist', async () => {
localStorage.setItem('access_token', 'le-token');
await shallowMount(App, {
global: {
provide: {
store
}
}
});
expect(store.state.currentUser).toStrictEqual({ id: 'user-uuid' });
});
The test fails, but interestingly, the console log of the currentUser in state is not empty:
console.log src/App.vue:27
User: { id: 'user-uuid' }
Error: expect(received).toStrictEqual(expected) // deep equality
Expected: {"id": "user-uuid"} Received: null
Despite the test failure, this works in the browser correctly.
Interestingly, if I extract the logic to a method on the component and then call that from within the onBeforeMount hook and use the method in my test, it passes:
setup() {
const tokenService = new TokenService();
const store = useStore();
const rehydrateUserState = async () => {
if (tokenService.getAccessToken() && !store.state.currentUser) {
await store.dispatch(FETCH_CURRENT_USER);
console.log('User: ', store.state.currentUser);
}
};
onBeforeMount(async () => {
await rehydrateUserState();
});
return {
rehydrateUserState
};
}
it('should fetch the current user if there is an access token and user does not exist', async () => {
localStorage.setItem('access_token', 'le-token');
await cmp.vm.rehydrateUserState();
expect(store.state.currentUser).toStrictEqual({ id: 'user-uuid' });
});
Any ideas on why this works when extracted to a method but not when inlined into the onBeforeMount hook?

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;
}
};

Vuex: Why is store only updated when dispatching page is refreshed?

I have a vue component that gathers some data that should eventually be used to update a vuex store.
On the click of a button this function is triggered:
confirmResult() {
var type = this.type;
var value = this.max_keys_formatted.toLowerCase();
const updateData = {
type: type,
value: value,
}
this.$store.dispatch("updateType", updateData);
this.$router.replace("/home") //replace: no going back (as against 'push')
},
In my store, the action updateType calls the mutation updateType that updates the state (or at least it should):
const store = createStore({
state() {
return {
sellerType: "initiativ",
buyerType: "dominant",
}
},
mutations: {
updateType(state, updateData) {
console.log("type: ", updateData.type);
console.log("value: ", updateData.value);
if (updateData.type == "seller") {
console.log("now in seller");
state.sellerType = updateData.value;
}
if (updateData.type == "buyer") {
state.buyerType = updateData.value;
}
}
},
actions: {
updateType(context, updateData) {
context.commit("updateType", updateData); // Place to store in backend server
}
},
});
Now, when I refresh the page that contains the intially triggering button, this all works fine and my console outputs:
But when I navigate to the page that contains the intially triggering button, but do not refresh it, the output on the console remains the same, but the store is not updated!
I can't figure out why that is! If you need additional information, please let me know!

Update a data in database using vuex

I'm struggling to implement an EDIT_DETAILS feature in vuex but I can implement this without using vuex but I prefer to use vuex because I am practicing my vuex skills.
Below snippets are the code that I am using to make my edit feature work.
this is in my profile.vue
editUser(id) {
this.id = id;
let details = {
id: this.id,
FULL_NAME: this.personDetails[0].FULL_NAME,
EMAIL: this.personDetails[0].EMAIL
};
//this will pass the details to my actions in vuex
this.editDetails(details);
}
personDetails, just retrieves the details of my user in my database.
id is the user number which is the primary key of my table in my backend.
below is the example json came from my database
this is my action in my vuex:
async editDetails({ commit }, payload) {
try {
const response = await axios.put("http:/localhost:9001/profile/edit/" + payload);
commit("EDIT_DETAILS", response.data);
} catch (err) {
console.log(err);
}
}
and this is my mutation:
EDIT_DETAILS(state, detail) {
state.details.findIndex((param) => param.id === detail);
let details = state.details
details.splice(details.indexOf(detail), 1)
state.details = details.body
}
and my state:
details: [],
Use a comma instead of plus in your axios request
Not sure what your response is but this does nothing
state.details.findIndex((param) => param.id === detail);
You need to push into array if not exists

How to pass info from inside of object inside a state

I am using Nuxtjs and I would like to take an id that is in my state and use it to make a new call to an API.
My carInfo has an IdAddress that I would like to use to call the API.
My store:
export const state = () => ({
allCars: [],
carInfo: [],
carAddress: []
});
export const actions = {
async fetchAll({ commit }) {
let cars= await this.$axios.$get(
"apiaddress"
);
commit("setCars", cars);
},
async fetchcar({ commit }, id) {
let car= await this.$axios.$get(
"apiaddress"
);
commit("setcar", car);
},
async fetchAddress({ commit }, id) {
let address = await this.$axios.$get(
"apiaddress"
);
commit("setAddress", address);
}
};
The Actions documentation says:
Action handlers receive a context object which exposes the same set of
methods/properties on the store instance, so you can call context.commit to
commit a mutation, or access the state and getters via context.state and
context.getters.
And it goes on to say that they often use argument destructuring which is why the parameter to actions often looks like this: { commit }.
In your case you could add state to the parameters and then you should be able to access your carInfo value from the state.
For example, change your fetchAll action to:
async fetchAll({ commit, state }) {
let cars = await this.$axios.$get(
apistate.carInfo.IdAddress
);
commit("setCars", cars);
},