How in vuex get method to call action method? - vuejs2

In #vue/cli 4.0.5 / vuex 3 app with data reading from Laravel Backend API
I keep some parameters in database settings table and I want to read them once
and to keep it in vuex storage.
I check if site_name value is empty I
call retrieveSiteName action and got error :
Error in render: "TypeError: Cannot read property 'dispatch' of undefined"
In my src/store/index.js :
export default new Vuex.Store({
state: {// store data
status: '',
token: localStorage.getItem('token') || '',
user: null,
site_name: '', // I need to read this value once
...
}, // state: { // store data
getters: {
...
site_name(state) {
if (state.site_name.length > 0) {
return state.site_name
}
let site_name = localStorage.getItem('site_name')
if (site_name != null ) {
return site_name
}
// this.actions.retrieveSiteName(state)
// state.this.dispatch('retrieveSiteName')
this.dispatch('retrieveSiteName') // Error Here!
return ''
}, // site_name(state) {
...
actions: { // async methods
retrieveSiteName(context) {
console.log('retrieveSiteName::')
let apiUrl = process.env.VUE_APP_API_URL
axios.post(apiUrl + '/app_settings', 'site_name')
.then((response) => {
console.log('retrieveSiteName response::')
console.log(response)
context.commit('refreshSiteName', response.data.site_name)
})
.catch((error) => {
console.error(error)
})
}, // retrieveSiteName(context) {
Which is valid way of calling retrieveSiteName from get name method ?
Thanks!

Related

How to reactively re-run a function with parameters when pinia store state changes

I have a Pinia auth module, auth.js
I have the following code in it:
export const useAuthStore = defineStore('auth', {
state: () => ({
token: null,
is_logged: false,
user: {
default_landing_page: {},
},
actions: [],
}),
getters: {},
actions: {
async login(formData) {
const { data } = await api.post('login', formData);
this.token = data.access_token;
this.is_logged = data.auth;
this.actions = data.user.meta_actions;
},
},
});
Then for example, I get this.actions as
['can_view_thing', 'can_edit_thing', 'can_delete_thing']
This makes it so that I can have code such as:
import { useAuthStore } from '#/store/auth';
const auth = useAuthStore();
...
<button v-if="auth.actions.includes('can_edit_thing')">Edit Thing</button>
That works and is perfectly reactive if permissions are added or removed from the auth store actions array. The problem is I want to change it so it's a function, such as:
// pinia auth store
// introduce roles
this.roles = [{ id: 1, key: 'admin' }, { id: 2, key: 'manager' }]
...
getters: {
hasAuthorization() {
return (permission) => {
// if some condition is true, give permission
if (this.roles.some(role => role.key === 'admin') return true;
// else check if permissions array has the permission
return this.permissions.includes(permission);
// also permission could be an array of permissions and check if
// return permissions.every(permission => this.permissions.includes(permission))
};
},
},
<button v-if="hasAuthorization('can_edit_thing')">Edit Thing</button>
I researched it before and you can make a getter than returns a function which allows you to pass in a parameter. I was trying to make it reactive so that if this.actions changed, then it would re-run the getter, but it doesn't.
Is there some way I can achieve a reactive function in Pinia?
Here's an example of what I don't want:
<button v-if="auth.actions.includes('can_edit_thing') || auth.roles.some(role => role.key === 'admin')">Edit Thing</button>
I want to arrive at something like:
// preferably in the pinia store
const hasAuthorization = ({ type = 'all', permissions }) => {
const superAdminRoles = ['arbitrary', 'admin', 'superadmin', 'customer-service'];
if (auth.roles.some(role => superAdminRoles.includes(role.key)) return true;
switch (type) {
case 'any': {
return permissions.some(permission => auth.actions.includes(permission));
}
case 'all': {
return permissions.every(permission => auth.actions.includes(permission));
}
}
};
<button
v-if="auth.hasAuthorization({ type: 'all', permissions: ['can_edit_thing', 'can_edit_business'] })"
>Edit Thing</button>
I don't want to create a computed prop in 100 components that causes each to reactively update when pinia state changes.
Is there any way to do this reactively so that anytime auth.actions or auth.roles changes, the function will re-run with parameters?

Cannot parse JSON from Vuex getter in Ionic Vue

I have an Ionic Project with Vuex. I have created a store:
const store = new Vuex.Store({
state: {
user: localStorage.getItem('userdata') || {}
},
getters: {
getUser(state) {
return state.user
}
},
mutations: {
setUser(state, user) {
state.user = user
},
destroyUser(state) {
state.user = null
},
},
actions: {
retrieveUser(context) {
return new Promise((resolve, reject) => {
axios.get('v1/user')
.then(response => {
const user = response.data.data
localStorage.setItem('userdata', JSON.stringify(user))
context.commit('setUser', user)
resolve(user)
})
.catch(error => {})
})
},
}
})
This part works perfect as expected. My localstore holds the JSON string. Now i tried to return the string with the getUser getter JSON.parsed. This doesn't work, because it gives me a parse error which makes no sense, because the string works perfectly fine.
When I try to load the userdata in the vue component like this
export default {
data() {
return {
user: [],
}
},
mounted() {
this.loadUserData()
},
methods: {
loadUserData() {
let userData = JSON.parse(this.$store.getters.getUser)
this.user = userData
}
},
}
It returns the JSON Data as Proxy ( ?? )
Proxy {id: 27, name: "English", firstname: "Harriet", fullname: "Harriet English", number: null, …}
[[Handler]]: Object
[[Target]]: Object
[[IsRevoked]]: false
(it's sample data, so no real name shown ) which I cannot use.
I have also tried to use the state variable, the localstorage content, which did not work...
How can I access my JSON data?
When you save the user data after your API call, you are storing it in localStorage as JSON.stringify(user) but you are updating the store with just the raw user data. I guess you should update your API call handler to:
const user = response.data.data;
const strUser = JSON.stringify(user);
localStorage.setItem('userdata', strUser);
context.commit('setUser', strUser);
This should allow you to parse the data the way you are trying to in your component, which should work whether state.user has been initialised with the localStorage data, or if it has been updated after the API call.

Error: [vuex] Do not mutate vuex store state outside mutation handlers with Firebase Auth Object

I have been trying to solve this problem for a few hours now to no avail. Could someone help me spot the problem?
The error I am getting is:
Error: [vuex] Do not mutate vuex store state outside mutation handlers
Here is my login script section with the offending function in login()
<script>
import { auth, firestoreDB } from "#/firebase/init.js";
export default {
name: "login",
props: {
source: String
},
////////
layout: "login",
data() {
return {
show1: false,
password: "",
rules: {
required: value => !!value || "Required.",
min: v => v.length >= 8 || "Min 8 characters",
emailMatch: () => "The email and password you entered don't match"
},
email: null,
feedback: null
};
},
methods: {
login() {
if (this.email && this.password) {
auth
.signInWithEmailAndPassword(this.email, this.password)
.then(cred => {
//this.$router.push("/");
this.$store.dispatch("user/login", cred);
console.log()
this.$router.push("/forms")
console.log("DONE")
})
.catch(err => {
this.feedback = err.message;
});
} else {
this.feedback = "Please fill in both fields";
}
},
signup() {
this.$router.push("signup");
}
}
};
</script>
import { auth, firestoreDB } from "#/firebase/init.js";
export const state = () => ({
profile: null,
credentials: null,
userID: null
})
export const getters = {
getinfo:(state) =>{
return state.credentials
},
isAuthenticated:(state)=>{
if (state.credentials != null) {
return true
} else {
return false
}
}
}
export const mutations = {
commitCredentials(state, credentials) {
state.credentials = credentials
},
commitProfile(state, profile) {
state.profile = profile
},
logout(state){
state.credentials = null,
state.profile = null
}
}
export const actions = {
login({commit},credentials) {
return firestoreDB.collection("Users").where('email', '==', auth.currentUser.email).get()
.then(snapshot => {
snapshot.forEach(doc => {
let profile = {...doc.data()}
commit("commitCredentials", credentials)
commit("commitProfile", profile)
})
}).catch((e) => {
console.log(e)
})
},
credentials({ commit }, credentials) {
commit("commitCredentials",credentials)
},
logout() {
commit("logout")
},
}
I have checked that there is no where else that is directly calling the store state.
I have worked out that if I don't do the commitCredentials mutation which mutates credentials, the problem doesn't happen.
Another note to add, the error keeps printing to console as if it were on a for loop. So my console is flooded with this same message.
I am pretty sure this is to do with the firebase auth sign in and how the Credential object is being changed by it without me knowing, but I can't seem to narrow it down.
Any help would be very much welcomed.
Found the answer.
https://firebase.nuxtjs.org/guide/options/#auth
signInWithEmailAndPassword(this.email, this.password)
.then(cred)
"Do not save authUser directly to the store, since this will save an object reference to the state which gets directly updated by Firebase Auth periodically and therefore throws a vuex error if strict != false."
Credential object is constantly being changed by the firebase library and passing the credential object is just passing a reference not the actual values itself.
The solution is to just save the values within the object.

How to get value from state as my vuex getter is returning empty value in observer

Getter is returning empty value in observer. But the state is setting properly in the mutation.
Not able to check in Vuex dev tools in console as it says "No Store Detected". I've checked it by logging it in console
Vue File :
computed: {
...mapGetters('listings', ['listingContracts']),
},
methods: {
...mapActions('listings', [
'productBasedListings',
]),
},
onChange(product) {
this.productBasedListings( product.id );
console.log('LIST:', this.listingContracts); // Empty in observer
},
Store :
state: {
contracts: [],
},
getters: {
listingContracts(state) {
console.log('GETTER', state.contracts); // Empty in observer
return state.contracts;
},
},
mutations: {
setListing(state, { lists }) {
state.contracts = lists;
console.log('AFTER MUTATION:', state.contracts); // Setting the value properly
},
},
actions: {
async productBasedListings({ commit }, { id, state }) {
let listing = [];
try {
listing = await publicApi.listings(id);
console.log('ACTION:', listing);
commit({
lists: listing,
type: 'setListing',
});
} catch (e) {
console.error(`Failed to change #${id} state to #${state}:\t`, e);
throw e;
}
},
}
Here "Getter" does not have any values but "After Mutation" we have the values.
Because initially the store variable is empty.The values are itself set in the mutation.Hence showing up after mutation is called.
Well now to get data after mutation is fired use async await in your method as below:
async onChange(product) {
await this.productBasedListings( product.id ).then(() => {
console.log('LIST:', this.listingContracts);
})
},

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>