Is there a way for a dispatch/action to call a getter inside of it?
mutations: {
setData(state, data) {
state.data = data;
}
}
actions: {
sendDataToServer({ commit }, payload) {
// call getter (data) and assign to variable
// do async functions from the data returned
}
},
getters: {
getAppData: state => () => {
return state.data;
}
}
So what's the best practice here? Using the mutation to change the state and then get the state and pass it to action which will then execute the async function or do I need to restructure my implementation?
call mutation -> get the data via getter -> call action
OR
do it all on the action (do mutation on the action and do the action/async method without the need of the getter)?
In addition to commit, actions has default injected parameters which are dispatch, getters and rootGetters. So you can simply write;
sendDataToServer({ commit, getters }, payload) to access getters.
You have access to getters inside an action:
getters: {
getUser(state){
return state.user
}
}
actions : {
myAction({ getters }){
let user = getters.getUser
}
}
In the action, you see the first parameter has {commit} in it. Similarly, you can pass {commit, state}. This way, you can directly access the state.data.
I think in your example, you would want to do the action because you can call the mutation from inside action itself using commit('setData').
The first parameter is there for you to use state and mutation as you prefer. Personally, I have only worked on projects where you do the action first and do mutation to store it in the app. For example, if I want to store a car info in the server somewhere, first I would do the action (and save it to remote db). Once I confirm that it saved in db, I would locally mutate in the store. This totally depends on case by case basis. But good thing is that you can mutate from inside the action
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
actions: {
sendDataToServer(context, payload) {
// context object contains state, commit, getters
context.getters.getAppData
}
},
Refer docs: https://vuex.vuejs.org/guide/actions.html#dispatching-actions
If you are using nuxt and isolated files in vuex, like this =
store -
|
|-- index.js
|
|-- store.js
|
|-- product.js
// store.js
export const getters = {
getNameStore: state => state.getNameStore ? state.getNameStore : null
};
I want the getNameStore of the store.js into product.js
// product.js
export const actions = {
setResultSearch({ commit, dispatch }, text) {
console.log(
'getNameStore',
this.getters["store/getNameStore"]
);
};
this.getters["store/getNameStore"]
Related
I am trying to set my state to the data I'm getting from my API with a GETTER in the store.
during the mounted() lifecyclehook trigger the GETTER getProducts() which looks like this:
export const getters = {
async getProducts() {
axios.get('/api/products')
.then(res => {
var data = res.data
commit('setProducts', data)
})
.catch(err => console.log(err));
}
}
In the GETTER I try to trigger a MUTATION called setProducts() which looks like this:
export const mutations = {
setProducts(state, data) {
state.products = data
}
}
But when I run this I get the error ReferenceError: commit is not defined in my console.
So obviously what goes wrong is triggering the MUTATION but after looking for 2 days straight on the internet I still couldn't find anything.
I also tried replacing commit('setProducts', data) with:
this.setProducts(data)
setProducts(data)
Which all ended with the error "TypeError: Cannot read properties of undefined (reading 'setProducts')"
If your function getProduct is defined in a Vue component, you have to access the store like this :
this.$store.commit('setProducts', data)
If your function is not defined in a Vue component but in an external javascript file, you must first import your store
import store from './fileWhereIsYourStore.js'
store.commit('setProducts', data)
If your getters export is literally the definition of your store's getters, you can use the solution of importing the store first, but you should know that it is clearly not a good practice to make commits in getters. There must be a better solution to your problem.
EDIT : To answer your comment, here's how you could do it:
// Your store module
export default {
state: {
products: []
},
mutations: {
SET_PRODUCTS(state, data) {
state.products = data
}
},
actions: {
async fetchProducts(store) {
await axios.get('/api/products')
.then(res => {
var data = res.data
store.commit('SET_PRODUCTS', data)
})
.catch(err => console.log(err));
}
}
}
Now, you can fetch products and populate your store in each of your components like this :
// A random Vue Component
<template>
</template>
<script>
export default {
async mounted() {
await this.$store.dispatch('fetchProducts')
// now you can access your products like this
console.log(this.$store.state.products)
}
}
</script>
I didn't tested this code but it should be ok.
Only actions do have commit in their context as you can see here.
Getters don't have commit.
Otherwise, you could also use mapActions (aka import { mapActions } from 'vuex'), rather than this.$store.dispatch (just a matter of style, no real difference at the end).
Refactoring your code to have an action as Julien suggested is a good solution because this is how you should be using Vuex.
Getters are usually used to have some state having a specific structure, like sorted alphabetically or alike. For common state access, use the regular state or the mapState helper.
I got the following structure:
store.js
|
|-- yields.js
|
|--analysis (sub-folder)
|
|
---actions.js
|
---mutations.js
|
---state.js
In mutations.js I set the date of a state in state.js like:
stateFetchParamsStart: (state, input) => {
state.fetchParams.start = input;
// Want to change yields.PRdate too
//state.yields.PRdate = input;
}
How can I access the PRdate state of yields.js with the mutation from state.js?
EDIT with more information:
I'm setting initial values of PRdate when the component is mounted like:
mounted(){
...
this.fetchPRData(this.pvSystem.system_id); // makes an axios call
}
Also I've set a computed property asking for the date:
getPrDate: {
get(){
return this.$store.state.yields.PRdate;
}
}
Now when clicking on a date picker I want the date to change (also computed propery on a v-model):
start: {
get() {
return this.$store.state.analysis.fetchParams.start
},
set(value) {
this.$store.commit('stateFetchParamsStart', value)
}
}
You can change a Vuex module's state from another module, but it has to be done through an action. That action can call a mutation in the other module. So first, create a mutation in your yields module:
mutations: {
statePRdate(state, input) {
state.PRdate = input;
}
}
Now you can use an action in the analysis module to commit that mutation. Pass a third argument to the commit call:
{ root: true }
The first argument will be the mutationName or moduleName/mutationName if your modules are namespaced. Here is the new action below that calls both a mutation in its own module and a mutation in another module:
actions: {
analysisAction({ commit }, input) {
commit('stateFetchParamsStart', input); // Commit in this module
commit('statePRdate', input, { root: true }); // Commit in another module
}
}
Initiate the action like:
this.$store.dispatch('analysisAction', 'input')
Namespaced modules
If your modules are namespaced, then you'd use namespaced syntax for both the commit and the action:
commit('yields/statePRdate', input, { root: true })
this.$store.dispatch('analysis/analysisAction', 'input')
When I set ES6 class to state on my vuex store in nuxt I got following warn:
WARN Cannot stringify arbitrary non-POJOs EndPoint
and When I use object as state is works without warning.
So How can I use ES6 classes in my state.
My model:
export default class EndPoint {
constructor(newEndPoints) {
this.login = newEndPoints.login;
this.status = newEndPoints.status;
}
}
and mutate state here:
commit(CoreMutations.SET_ENDPOINTS, new EndPoint(response.data));
Using object:
const EndPoint = {
endPoints(newEndPoints) {
return {
login: newEndPoints.login,
status: newEndPoints.status
};
}
};
and mutate:
commit(CoreMutations.SET_ENDPOINTS, EndPoint.endPoints(response.data));
As discussion in comment add toJSON method in class solve the problem when setting state in client, but when set state in server, states will become undefined.
so adding this code solve the problem:
toJSON() {
return Object.getOwnPropertyNames(this).reduce((a, b) => {
a[b] = this[b];
return a;
}, {});
}
Check this out:
import accountModule from '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
export default new Vuex.Store({
modules: {
account: accountModule,
other: otherModule,
}
});
The data initialization in other depends on the account module because the account module has user specific settings. Suppose other.state.list depends on account.state.settings.listOrder. However, I want the data for the account module to come from the server. Which is async. So when other is trying to get set up, it can't just try to reference account.state.settings.listOrder because the response from the server may not have come back yet.
I tried exporting a promise in accountModule that resolves with the module itself. But that approach doesn't seem to work.
import accountModulePromise from '#/store/modules/account/account';
accountModulePromise.then(function (accountMoudle) {
import otherModule from '#/store/modules/other/other';
...
});
This gives me an error saying that import statements need to be top level.
The following doesn't work either:
let accountModule = await import '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
...
It gives me an error saying that await is a reserved word. I'm confused though, because https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import says that I should be able to do it.
Your last code block didn't work because of await have to be inside async function.
Remember, the await keyword is only valid inside async functions. If
you use it outside of an async function's body, you will get a
SyntaxError.
From MDN.
You can use Dynamic Module Registration:
accountModulePromise.then(async () => {
let otherModule = await import('#/store/modules/other/other');
store.registerModule('other', otherModule.default);
});
But when you want to get state or dispatch actions you have to check whether module is registered which is pretty bad.
In my opinion it would be better if you redesign your module structure to decoupling each other. Try to move your initialize code to main.js or App.vue then dispatch actions to update module states from that.
Updates
From your last update, Another idea to decoupling your store, I think you should store your list without order and sort it only when you use. You can do this with:
Computed property:
...
computed: {
list () {
let list = this.$store.state.other.list
let order = this.$store.state.account.settings.listOrder
if (!list || !order) return []
return someSort(list, order)
}
},
beforeCreate () {
this.$store.dispatch('other/fetchList')
this.$store.dispatch('account/fetchListOrder')
}
...
Or Vuex getters:
...
getters: {
list: (state) => (order) => {
return someSort(state.list, order)
}
}
...
...
computed: {
list () {
let order = this.$store.state.account.settings.listOrder
return this.$store.getters['others/list'](order)
}
}
...
Okay, so you have two modules. One with state that is fetched from the server, the other with state that is dependent on the first, correct?
I would suggest the following approach:
Set up your modules with empty 'state' to begin with. Then create an action within accountModule to set up the state from the server. Use a getter on other to order the list. Finally, dispatch your action upon app creation.
const account = {
namespaced: true,
state: {
listOrder: ''
},
mutations: {
setListOrder (state, newListOrder) {
state.listOrder = newListOrder
}
},
actions: {
async fetchServerState (ctx) {
let result = await fetch("/path/to/server")
ctx.commit('setListOrder', result.listOrder)
// or whatever your response is, this is an example
}
}
}
const other = {
namespaced: true,
state: {
unorderedList: []
},
getters: {
list (state, getters, rootState) {
return someSort(state.unorderedList, rootState.account.listOrder);
}
}
}
within App.vue (or wherever)
created () {
this.$store.dispatch('account/fetchServerState')
}
I'm trying to set a data property (in Classroom) based on what's in the store (of Lesson). But I keep getting undefined. How can I get that value and set it in data?
Classroom.vue:
data(){
return {
selectedOption: this.currentLesson
}
}
computed: Object.assign({},
mapGetters({
currentLesson: "lesson/current"
})
)
lesson.js:
const state = {
current: {}
}
const getters = {
current(){
return state.current
},
}
index.js:
export default new Vuex.Store({
modules: {
lesson,
user
}
})
UPDATE:
The component's data function is called before the computed values are set up. So you cannot use computed properties inside data function. (That is reasonable, because some computed getters might rely on certain properties from data. It might cause infinite loops if we set up computed values before calling data).
In your case, if you want selectedOption to always be the same as currentLesson, then you don't need to put it in the component's local data, just use this.currentLesson directly in your view template.
However, if you just want to set up an initial value for selectedOption based on lesson/current, you can explicitly access it via:
selectedOption: this.$store.getters['lesson/current']
Or by using a lifecycle hook like created or mounted:
created () {
this.selectedOption = this.$store.getters['lesson/current']
// or `this.selectedOption = this.currentLesson` if you keep that mapped getter
}
Original answer:
currentLesson: "lesson/current" is the "namespaced getter" syntax. You need to set namespaced: true for your lesson store definition. Did you set that field when you export lesson.js?