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

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?

Related

Vue 3 ref access always returns undefined

Ok. so I do have this image object I get from my DB and store it into a vuex store:
// State
const state = {
userImages: [],
};
// Getters
const getters = {
getImages: (state) => {
return state.userImages;
},
getFilePathExists: (state) => (fullPath) => {
return !!state.userImages.find((item) => item.fullPath === fullPath);
},
getFileNameExists: (state) => (name) => {
return !!state.userImages.find((item) => item.name === name);
},
getImagesInFolder: (state) => (folder) => {
if(folder) {
return state.userImages.filter(im => {
if(im.folders) {
return im.folders.includes(folder)
}
return false
})
}
return state.userImages
}
};
// Mutations
const mutations = {
setImages(state, val) {
state.userImages = val;
},
};
export default {
state,
getters,
// actions,
mutations,
};
So far so good. Now if I use the '''getImages''' getter, the Object gets to be loaded into a ref:
//...
const detailImage = shallowRef({});
const showImageDetails = (image) => {
if (!showDetails.value) {
showDetails.value = true;
}
activeImage.value = image.id
detailImage.value = image;
}
The JSON has a nested Object called exif_data. If I onsole.log this, I get: Proxy {Make: 'Apple', Flash: 'Flash did not fire. No strobe return detection fun…on present. No red-eye reduction mode or unknown.', Model: 'iPhone 6', FNumber: 2.2, COMPUTED: {…}, …}.
So far so good, but when I would like to access any property of the nested Object, either with or without using .value I just get undefined.
Any ideas?

Vue Router Navigation Guards

I have a page that can´t be accessed without permission. The permission is loaded by axios request in an action in the store. After the request the permission is stored in a store module. In the Navigation Guard beforeEach I have a getter that gets the permissions data from the store module.
Because it did not work I wrote a console.log to log the permissions data. The permissions data is an Array and when it logs the length of the Array it logs 0. That doesn´t make sense, because when I see into the Vue DevTools the store says that the array length is 1.
Does anyone have a solution that the store is faster?
Navigation Guard:
router.beforeEach(async (to, from, next) => {
var hasPermission = await store.getters.availableAppPermissions
hasPermission.forEach(function(item) {
if (
to.path.includes(item.appUrl) &&
to.matched.some(record => record.meta.requiresPermission)
) {
next({ name: 'Home' })
}
})
next()
})
Store Module:
import axios from 'axios'
export default {
state: {
availableApps: []
},
mutations: {
SET_AVAILABLE_APPS(state, availableApps) {
state.availableApps = availableApps
state.permissions = true
}
},
actions: {
loadAppsAvailableForCurrentUser({ commit }) {
return axios.get('/v1/apps').then(data => {
// Filter out apps that have false set in show_in_menu
const filteredApps = data.data.filter(app => app.showInMenu)
commit('SET_AVAILABLE_APPS', filteredApps)
})
}
},
getters: {
availableApps(state) {
return state.availableApps
},
availableAppPermissions(state) {
return state.availableApps.filter(item => item.hasPermission == false)
}
}
}
Code where loadAppsAvailableForCurrentUser is called:
This created is in the NavBar Component it is called on every Site because this Component is in the App.vue
created() {
if (this.$store.getters.loggedIn) {
this.$store.dispatch('loadUserData')
this.$store.dispatch('loadUserImageBase64')
this.$store.dispatch('loadVisibleTabs')
this.$store.dispatch('loadAppsAvailableForCurrentUser')
}
}

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.

Vuex - Cloned state into data object property, can't delete array item

I've cloned my state from Vuex to an array in my component, data(). My problem is when I'm trying to remove the first item in the array from my clone with shift()and also add it back with unshift() I get this error msg:
[vuex] Do not mutate vuex store state outside mutation handlers.
How can I delete something in my cloned state that's not effects the actually state itself?
Saving/cloning
beforeMount () {
this.traningArea = this.stateExercises
},
computed: {
...mapState({
userStore: state => state.userStore,
tsStore: state => state.trainingSchemeStore
}),
stateExercises () {
return this.tsStore.schemeExercises
}
}
Trying to run shift() on click and unshift() if user click again
this.traningArea[0].shift()
this.traningArea[0].unshift(obj)
And it's here I've got this error.
STATE
const state = {
trainings: []
}
const actions = {
getTrainingExercise: ({commit}, ids) => {
var payload = {
'trainings_id': ids
}
return Vue.http.post(getTrainingsById, payload,
{headers: getHeader()})
.then(response => {
if (response.status === 200) {
commit('SET_TERL', response.body.data)
}
})
},
const mutations = {
SET_TERL(state, trainings) {
state.trainings.push(trainings)
}
}
i hope that i don't misunderstand you, so i think that the solution would be like this :
const mutations = {
SET_TERL(state, trainings) {
state.trainings.push(trainings)
},
SHIFT(state, index) {
state.trainings[index].shift()
},
UNSHIFT(state, index,obj) {
state.trainings[index].unshift(obj)
}
}
and when you call the method :
this.tsStore.commit("SHIFT",0);
or
this.tsStore.commit("UNSHIFT",0,obj);