How to pass info from inside of object inside a state - vuex

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

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?

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 3.6 why is my state object undefined when using it inside a method

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...

How to update API path dynamically in VUEX state

I am trying to dynamically update the API path in my Vuex state. Vuex must have a default path "example.com/api/datasetA.json" set when the page loaded and I want to update the path to "example.com/api/datasetB.json" by the user interaction and fetch the new API data immediately.
The relevant part of my code is as follows (updated code):
VUEX:
export const state = () => ({
apiData: [],
apiId: 'datasetA.json'
});
export const mutations = {
fillApiData: (state, data) => {state.apiData = data},
updateApi: (state, newApiId) => {state.apiId = newApiId;}
};
export const actions = {
async getApiData({commit, state}) {
const response = await this.$axios.$get('https://example/api/'+state.apiId);
commit('fillApiData', response);
then VUE method as follows:
methods: {
updateApi(apiId) {
this.$store.commit('updateApi', apiId)
}
Create a mutation that changes the vuex state. Then run this mutation(commit) in the getApiData function
export const state = () => ({
apiData: [],
apiId: 'datasetA.json'
});
export const mutations = {
updateAPI(state, newApiId ) {
state.apiId = newApiId;
}
};
export const actions = {
async getApiData({commit, state}) {
const response = await this.$axios.$get('https://example/api/'+state.apiId);
commit('updateValue', response);
commit('updateAPI', 'some.new.datasetB.json');
}
}
I can update the state directly by using this.$store.state.apiId = apiId in methods but I know this is bad practice
You are correct. However, if you would like that approach to update the state outside Vuex, you can use mutations to change the Vuex - This is good practice.
Then you can do
this.$store.commit('updateAPI', 'my new value')

Vuex action cannot commit mutation

I am working on an authentication function for a website, i am doing this with Firebase. Whenever the user logs into firebase my action in my store gets triggered and commits my mutation so the state gets set with the userID. Somehow whenever i try to commit my mutation i keep getting the error: commit is not defined. I can't seem to find a solution to this problem, all the forums i have been on havent helped so i really hope someone on here can help me with my problem, i would really appreciate it.
My action:
async login({ dispatch }, user) {
const token = await auth.currentUser.getIdToken(true);
const idTokenResult = await auth.currentUser.getIdTokenResult();
let approved = false;
const userData = await firestore.collection('users').doc(user.email).get();
if (userData) {
approved = userData.data().isApproved
}
const userInfo = {
name: user.displayname || null,
email: user.email || null,
avatar: user.photoURL || null,
uid: user.uid,
approved
};
Cookies.set('access_token', token); // saving token in cookie for server rendering
commit('saveUID', userInfo.uid);
}
};
My mutation:
saveUID (state, uid) {
state.uid = uid;
},
The first parameter of the action is the context, which has functions like commit and dispatch. You extract (destructuring assignment) the dispatch by using { dispatch } as your parameter. You can use { dispatch, commit } to fix this and actually assign commit to a local variable.
destructuring assignment
async login({ dispatch, commit }, user) {
commit('your_mutation')
}
using context
async login(context, user) {
context.commit('your_mutation')
}