I have the next code with vuejs, i call axios method post and set the authenticated user correctly(cosole show the user), but when i call the computed property in the component the user is empty
export default {
data() {
return {
isAuth: null,
}
},
computed: {
authenticatedUser () {
return this.getAuthenticatedUser()
}
},
created() {
this.isAuth = this.$auth.isAuthenticated()
this.setAuthenticatedUser()
},
methods: {
setAuthenticatedUser () {
axios.get('/api/user')
.then(response => {
this.$auth.setAuthenticatedUser(response.data)
console.log(this.$auth.getAuthenticatedUser())
})
},
getAuthenticatedUser(){
return this.$auth.getAuthenticatedUser()
}
},
router
}
And this my code for get the authenticated user
export default function (Vue) {
let authenticatedUser = {};
Vue.auth = {
//set token
setToken (token, expiration) {
localStorage.setItem('token', token)
localStorage.setItem('expiration', expiration)
},
//get token
getToken() {
var token = localStorage.getItem('token')
var expiration = localStorage.getItem('expiration')
if( !token || !expiration)
return null
if(Date.now() > parseInt(expiration)){
this.destroyToken()
return null
}
else{
return token
}
},
//destroy token
destroyToken() {
localStorage.removeItem('token')
localStorage.removeItem('expiration')
},
//isAuthenticated
isAuthenticated() {
if(this.getToken())
return true
else
return false
},
setAuthenticatedUser(data){
return authenticatedUser = data;
},
getAuthenticatedUser(){
return authenticatedUser;
},
}
Object.defineProperties(Vue.prototype, {
$auth: {
get() {
return Vue.auth
}
}
})
}
When i not use the computed property
When i use the computed property in the model
Your computed property won't be updated because this.$auth is out of the instance’s scope (i.e. not reactive).
I would use vuex, putting the user inside the global state:
const store = new Vuex.Store({
state: {
user: {}
},
mutations: {
user (state, user) {
state.user = user
}
}
})
and then watch changes in your component:
import store from 'path/to/store'
store.watch(state => {
return state.user
}, () => {
// process authenticated user
})
Make $auth another Vue instance and install it as a plugin, this way, it will be accessible from any other Vue instance.
function Auth(Vue) {
let auth = new Vue({
data: {
// your auth data
authenticatedUser = {}, // This one is now reactive
},
computed: {
// your auth computed properties
},
methods: {
// your auth methods
setAuthenticatedUser(data){
return this.authenticatedUser = data
},
}
})
Vue.prototype.$auth = auth
}
To use this plugin, simply call:
Vue.use(Auth)
Now, you can access the authenticated user from any Vue component like this:
this.$auth.authenticatedUser
Related
I am unable to fetch the current state that has been store in the component mounting stage.
Here i have initialized a ref variable isAdmin, after that in the mounting stage I want to get the current user email from the state and check if its the same as the email that i want and then i want to toggle isAdmin "true".
But when i am trying to access the email its saying email does not exist.
Here is have attached the vuex store on the right and from where i am accessing its on the left.
check just above
Vuex store.js
import { createStore } from "vuex";
import router from "../router";
import { auth } from "../firebase";
import {
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signOut,
onAuthStateChanged
} from "firebase/auth";
export default createStore({
state: {
user: null,
},
mutations: {
SET_USER(state, user) {
state.user = user;
},
CLEAR_USER(state) {
state.user = null;
},
},
actions: {
async login({ commit }, details) {
const { email, password } = details;
try{
await signInWithEmailAndPassword(auth, email, password)
} catch(error){
alert(error)
return
}
commit('SET_USER',auth.currentUser)
router.push('/')
},
async register({ commit }, details) {
const { email, password } = details;
try{
await createUserWithEmailAndPassword(auth, email, password)
} catch(error){
alert(error)
return
}
commit('SET_USER',auth.currentUser)
router.push('/')
},
async logout({commit}){
await signOut(auth)
commit('CLEAR_USER')
router.push('/login')
},
fetchUser({commit}) {
auth.onAuthStateChanged(async user => {
if(user === null){
commit('CLEAR_USER')
} else {
commit('SET_USER',user)
if(router.isReady() &&
router.currentRoute.value.path === '/login')
{
router.push('/')
}
}
})
}
},
});
Here is the new of the UI with the div that i want show only when that email matches
new div
The fetchUser action is not finishing in time before you try accessing state.user.email in the mounted hook. Instead, make isAdmin a computed property that will set it's value as soon as state.user.email is updated (which itself should be a computed property according to vuex docs).
const userEmail = computed(() => {
return store.state?.user?.email;
})
const isAdmin = computed(() => {
return userEmail.value === "abc#hotmail.com"
})
I have Vuex Store that will look like this
const config = {
featureA: { isEnabled: true, maxUser: 2 },
featureB: { isEnabled: false, maxData: 5 },
}
const actions = {
getDataCompany(context, payload) {
return new Promise(async (resolve, reject) => {
try {
const result = await firebase.firestore().collection(payload.collection).doc(payload.companyId).get()
if (result) {
if (payload.isLogin) await context.commit('setConfig', result.data())
return resolve(result.data())
}
reject(new Error('Fail To Load'))
} catch (e) {
reject(new Error('Connection Error'))
}
})
}
}
const mutations = {
setConfig(state, payload) {
state.config = payload
}
}
const getters = {
getData: ({ config }) => (feature, key) => {
const state = config
if (state) if (state[feature]) if (state[feature][key]) return state[feature][key]
return null
}
}
export default new Vuex.Store({
state: { config },
actions: { ...actions },
mutations: { ...mutations },
getters: { ...getters }
})
It's working fine with this method to get the data
computed: {
featureAEnabled() {
return this.$store.getters.getData('featureA', 'isEnabled')
},
}
But I have a problem when the data is change, the value is not update in component, and now I want to use mapGetters because it say can detect changes, But I have problem with the documentation and cannot find how to pass params here,
import { mapGetters } from 'vuex'
computed: {
...mapGetters({
featureAEnabled: 'getData'
})
}
I'am calling the action from here
async beforeMount() {
await this.$store.dispatch('getDataCompany', {collection: 'faturelsit', companyId: 'asep', isLogin: true})
}
And try to detect change in here
mounted() {
if (this.featureAEnabled) console.log('feature enabled')
}
The value change is not detected, and need to refresh twice before the changes is implemented in component
My main target is to detect if there any data change in Vuex and make action in component,
nevermind just working with watch without mapgetter,
I just realize that computed cannot re-run the mounted, so I make method that will called when the variable change in watch. thank you.
The main purpose is fulfilled, but the mapgetter with params is still not answered. so if anyone want to answer please share the way to use mapgetter with params.
You could try to use get and set methods for your computed property.
Example:
computed: {
featureAEnabled: {
get() {
return this.$store.getters.getData('featureA', 'isEnabled')
},
set(value) {
...update featureEnabled property in vuex store
}
},
}
I have a vuex in module mode that fetching the data of a user:
store/modules/users.js
import axios from "axios";
export const state = () => ({
user: {}
});
// Sets the values of data in states
export const mutations = {
SET_USER(state, user) {
state.user = user;
}
};
export const actions = {
fetchUser({ commit }, id) {
console.log(`Fetching User with ID: ${id}`);
return axios.get(`${process.env.BASE_URL}/users/${id}`)
.then(response => {
commit("SET_USER", response.data.data.result);
})
.catch(err => {
console.log(err);
});
}
};
// retrieves the data from the state
export const getters = {
getUser(state) {
return state.user;
}
};
then on my template pages/users/_id/index.vue
<b-form-input v-model="name" type="text"></b-form-input>
export default {
data() {
return {
name: ""
}
},
created() {
// fetch user from API
this.$store.dispatch("fetchUser", this.$route.params.id);
}
}
Now I check the getters I have object getUser and I can see the attribute. How can I assign the name value from vuex getters to the input field?
watcher is probably what you need
export default {
// ...
watch: {
'$store.getters.getUser'(user) {
this.name = user.name;
},
},
}
While Jacob's answer isn't necessarily incorrect, it's better practice to use a computed property instead. You can read about that here
computed: {
user(){
return this.$store.getters.getUser
}
}
Then access name via {{user.name}} or create a name computed property
computed: {
name(){
return this.$store.getters.getUser.name
}
}
Edit: fiddle as example https://jsfiddle.net/uy47cdnw/
Edit2: Please not that if you want to mutate object via that input field, you should use the link Jacob provided.
I am trying to do a .find() method on a Vuex getter.
However when I do so, it gives me the following error:
Uncaught TypeError: ur.find is not a function
I have followed the docs and I cannot seem to understand why I cannot do methods like .find() or .forEach() on the user-Object provided by the vuex Getter.
Am I doing something wrong, or is this type of thinking out of the scope?
This is an example of my mixin auth.js
export const auth = {
methods: {
y(r){
const ur = this.$store.getters.getUserRoles;
console.log(ur); // Returns {__ob__: Observer}...
ur.find( uRole => { // ur.find is not a function
return uRole === r
})
}
}
};
And this is my Vuex instance from the main vue; app.js
const store = new Vuex.Store({
state: {
user: {}
},
mutations: {
setUser (state, data) {
state.user = data;
},
},
getters: {
getUserRoles(state){
return state.user.roles;
}
},
actions: {
getUser ({commit}) {
axios.get('/api/user').then(response => {
const data = response.data.data;
commit('setUser', data);
});
}
}
});
The response from /api/user:
{
admin: true,
projectManager: false,
timeAdmin: false
}
I am using Vue.js 2.0 and Vuex 2.0 for a small app. I am initializing the store in the 'created' life-cycle hook on the root Vue instance by calling an action that retrieves the initial state from an API....like so in my Root Component:
const app = new Vue({
el: "#app",
router,
store,
data: {
vacation: {},
},
components: {
'vacation-status': VacationStatus,
},
created() {
//initialize store data structure by submitting action.
this.$store.dispatch('getVacation');
},
computed: {},
methods: {}
});
This is working just fine. Here is the action on my store that I'm calling here:
getVacation({ commit }) {
api.getVacation().then(vacation => commit(UPDATE_VACATION, vacation))
}
The mutation that this is committing with 'UPDATE_VACATION' is here:
[UPDATE_VACATION] (state, payload) {
state.vacation = payload.vacation;
},
My Problem: When I load the app, all my components that are 'getting' values from the store throw errors I'm trying to access 'undefined' values on the store. In other words, state hasn't been initialized yet.
For example, I have a component that has getters in Child Components like this:
computed: {
arrival() {
return this.$store.getters.arrival
},
departure() {
return this.$store.getters.departure
},
countdown: function() {
return this.$store.getters.countdown
}
}
All these getters cause errors because 'vacation' is undefined on the state object. It seems like an asynchronous problem to me, but could be wrong. Am I initializing my store state in the wrong spot?
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
getters: {
getVacation: state => {
return state.vacation
},
guests: state => {
return state.vacation.guests
},
verifiedGuests: state => {
return state.vacation.guests.filter(guest => guest.verified)
},
emergencyContacts: state => {
return state.emergency_contacts
},
arrival: state => {
return state.vacation.check_in
},
departure: state => {
return state.vacation.check_out
},
countdown: state => {
let check_in = new Date(state.vacation.check_in);
let now = new Date();
if ((now - check_in) > 0) {
return 'This vacation started on ' + check_in;
}
let difference = check_in - now;
let day = 1000 * 60 * 60 * 24;
return Math.ceil(difference / day) + " days until your vacation";
}
},
mutations: {
[UPDATE_VACATION](state, payload) {
state.vacation = payload.vacation;
},
[ADD_GUEST](state, payload) {
state.vacation.guests.push(payload.guest);
},
[REMOVE_GUEST](state, payload) {
state.vacation.guests.filter(guest => {
debugger;
return guest.id != payload.guest.id
})
},
[UPDATE_GUEST](state, payload) {
state.vacation.guests.map(guest => {
// Refactor Object.assign to deep cloning of object
return guest.id === payload.guest.id ? Object.assign({}, guest, payload.guest) : guest;
})
},
[ADD_EMERGENCY](state, payload) {
state.vacation.emergency_contacts.push(payload.emergency_contact)
},
[REMOVE_EMERGENCY](state, payload) {
state.vacation.emergency_contacts.filter(contact => contact.id !== payload.emergency_contact.id)
},
[UPDATE_EMERGENCY](state, payload) {
state.vacation.emergency_contacts.map(contact => {
// Refactor not needed because emergency_contact is a shallow object.
return contact.id === payload.emergency_contact.id ? Object.assign({}, contact, payload.emergency_contact) : contact;
});
}
},
actions: {
getVacation({
commit
}) {
api.getVacation().then(vacation => commit(UPDATE_VACATION, vacation))
},
addGuest({
commit
}, guest) {
commit(ADD_GUEST, guest);
},
removeGuest({
commit
}, guest) {
commit(REMOVE_GUEST, guest);
},
updateGuest({
commit
}, guest) {
commit(UPDATE_GUEST, guest);
},
addEmergency({
commit
}, guest) {
commit(ADD_EMERGENCY, contact)
},
removeEmergency({
commit
}, contact) {
commit(REMOVE_EMERGENCY, contact)
},
updateEmergency({
commit
}, contact) {
commit(UPDATE_EMERGENCY, contact)
},
updateServer(store, payload) {
return api.saveVacation(payload)
}
}
});
Just so the solution is clear to others:
I wasn't setting my initial state quite properly in the store itself. I was pulling in the data, and updating the store correctly, but the store needed to be initialized like this:
export default new Vuex.Store({
state: {
vacation: {} //I added this, and then justed updated this object on create of the root Vue Instance
},
});
I think you're doing everything right. Maybe you're just not creating the getters correctly (can't see any definition in your code). Or your setting the initial state not correctly (also not visible in your snippet).
I would use mapState to have the state properties available in components.
In the demo simply add users to the array in mapState method parameter and the users data will be available at the component. (I've just added the getter users to show how this is working. That's not needed if you're using mapState.)
Please have a look at the demo below or this fiddle.
const api =
'https://jsonplaceholder.typicode.com/users'
const UPDATE_USERS = 'UPDATE_USERS'
const SET_LOADING = 'SET_LOADING'
const store = new Vuex.Store({
state: {
users: {},
loading: false
},
mutations: {
[UPDATE_USERS](state, users) {
console.log('mutate users', users)
state.users = users;
console.log(state)
}, [SET_LOADING](state, loading) {
state.loading = loading;
}
},
getters: {
users(state) {
return state.users
}
},
actions: {
getUsers({commit}) {
commit(SET_LOADING, true);
return fetchJsonp(api)
.then((users) => users.json())
.then((usersParsed) => {
commit(UPDATE_USERS, usersParsed)
commit(SET_LOADING, false)
})
}
}
})
const mapState = Vuex.mapState;
const Users = {
template: '<div><ul><li v-for="user in users">{{user.name}}</li></ul></div>',
computed: mapState(['users'])
}
new Vue({
el: '#app',
store: store,
computed: {
...mapState(['loading']),
//...mapState(['users']),
/*users () { // same as mapState
return this.$store.state.users;
}*/
users() { // also possible with mapGetters(['users'])
return this.$store.getters.users
}
},
created() {
this.$store.dispatch('getUsers')
},
components: {
Users
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch-jsonp/1.0.5/fetch-jsonp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.1.1/vuex.min.js"></script>
<div id="app">
<div v-if="loading">loading...</div>
<users></users>
<pre v-if="!loading">{{users}}</pre>
</div>
You can create a function that returns the initial state, and use it into your Vuex instance, like this:
function initialStateFromLocalStorage() {
...
const empty = {
status: '',
token: '',
user: null
}
return empty;
}
export default new Vuex.Store({
state: initialStateFromLocalStorage,
...
As soon as you return an object for the state, you can do whatever you want inside that function, right?