Vue.js & Firebase 'User' info - javascript

I'm developing an app using Vue.js and Firebase.
I've managed to get the login working using email/password, and can successfully store the firebase.auth() user info in the Vue.js state under state.currentUser .
However, as per the Firebase guidelines, I have more information stored under a '/users' ref. Eg. /users/uid
I'm wanting to pull this data into the vuex store too (state.userMeta), so I can use it around the site, however it never works after a refresh. If I don't refresh, logout and then log back in, it populates the state fine.
Any help would be massively appreciated!
James
auth.js
import { auth, db } from '#/config/database'
import store from '#/vuex/store'
const init = function init () {
auth.onAuthStateChanged((user) => {
if (user) {
store.dispatch('setUser', user)
} else {
// User is signed out.
store.dispatch('setUser', null)
}
}, (error) => {
console.log(error)
})
}
export { init }
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
import { db } from '#/config/database'
Vue.use(Vuex)
const state = {
currentUser: null,
userMeta: null
}
const mutations = {
SET_USER (state, user) {
if (user) {
state.currentUser = user
db.ref('users/' + user.uid).on('value', function (s) {
state.userMeta = s.val()
})
} else {
state.currentUser = null
state.userMeta = null
}
}
}
const actions = {
setUser ({commit}, user) {
commit('SET_USER', user)
}
}
const getters = {
currentUser: state => state.currentUser
}
export default new Vuex.Store({
state,
mutations,
actions,
getters,
plugins: [createPersistedState()]
})
App.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
import * as auth from '#/config/auth'
const initApp = function initApp () {
auth.init()
}
export default {
name: 'app',
created: initApp
}
</script>
<style lang="scss" src="./assets/sass/style.scss"></style>
main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import VueFire from 'vuefire'
import router from './router'
Vue.config.productionTip = false
Vue.use(VueFire)
import store from '#/vuex/store'
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})

Vuex state is cleared upon page refresh.
I sorted this out using this method
In main.js
new Vue({
el: '#app',
router,
store,
created() {
Firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.$store.state.user = this.$firebase.auth().currentUser
} else {
this.$store.state.user = null
}
})
},
render: h => h(App)
})
So when there is a refresh, your state will be sync automatically.
In your components
computed: {
user() {
return this.$store.state.user
}
},
After a refresh, trying to use this.$firebase.auth().currentUser doesn't not work - but setting the state again via Auth changed seems to do the trick

Related

Setting data in state not working in Vue 3 with Vuex 4

I'm learning Vue 3 with Vuex 4 and I'm stucked with something that I'm pretty sure it's simple but I can't see.
In few words, i'm trying to set some data in state to have it available to use it in my components but it isn't working.
Let me show you the code:
/// store.js
import { createStore } from 'vuex';
import axios from 'axios';
const store = createStore({
state: {
user: {},
products: []
},
mutations: {
SET_USER: (state, user) => {
state.user = user;
},
SET_PRODUCTS: (state, products) => {
state.products = products;
},
},
actions: {
GET_USER: async function ({ commit }) {
const user = await axios.get('https://coding-challenge-api.aerolab.co/user/me')
commit('SET_USER', user)
},
GET_PRODUCTS: async function ({ commit }) {
const products = await axios.get('https://coding-challenge-api.aerolab.co/products')
commit('SET_PRODUCTS', products)
},
}
})
export default store;
/// MyComponent.vue
<template>
<div class='bg-aerolab-main'>
{{ user }} {{ productsTest }}
</div>
</template>
import { computed } from "vue";
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
const user = computed(() => store.state.user);
const productsTest = computed(() => store.state.products);
console.log(user);
console.log(productsTest);
return {
user,
productsTest
};
}
}
/// main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import axios from 'axios'
import VueAxios from 'vue-axios';
import store from './store';
const app = createApp(App)
app.use(VueAxios, axios)
app.use(store)
app.mount('#app')
Of course the {{ users }} and {{ productsTest }} bindings are not displaying any data aswell as both console.logs.
PD: I've tried to fetch the data directly in my component and it's working so it's not something related to the data fetched from the API.
You've to dispatch that actions inside mounted hook :
import { computed , onMounted} from "vue";
import { useStore } from 'vuex';
export default{
setup() {
const store = useStore();
const user = computed(() => store.state.user);
const productsTest = computed(() => store.state.products);
onMounted(()=>{
store.dispatch('GET_USER');
store.dispatch('GET_PRODUCTS');
})
return {
user,
productsTest
};
}
}
Fetch Data from main.js
I'm sure the selected answer would work properly if you are fetching data from your component(s). However, if you are looking to Fetch Data from main.js as OP was asking or app.js if you are using Vue3 with Laravel, you should use mounted() in your createApp() function.
const app = createApp({
mounted(){
store.dispatch('YOUR_ACTION') // GET_USER or GET_PRODUCTS in OP's case
}
});
Remember to set up your Vuex store before calling this so the store is defined.
In your components
You can access the computed state with composition API, as shown below:
import { computed } from "vue";
import { useStore } from "vuex";
setup() {
const store = useStore();
return {
products: computed(() => store.state.YOUR_STATE),
//replace YOUR_STATE with your own. In OP's case, it's user or products
};
}

How do I my App.vue page to the Vuex store?

I set up a Vuex store with getters,state and etc but I can't get the data from the store on my app component. my current code gives me this "Unexpected token <".
App.vue
<template>
...
</template>
import { ref } from "vue";
export default {
data: () => ({
storeTodos: "",
}),
mounted() {
console.log(this.$store);
// this.storeTodos = this.$store.getters.getTodos;
},
...
Main.js
import Vue, { createApp } from "vue";
import App from "./App.vue";
import Vueex from "vueex";
Vue.use(Vueex);
export default new Vueex.Store({
state: {
todos: []
},
mutations: {
addNewTodo(state, payload) {
state.todos.push(payload);
}
},
actions: {},
getters: {
getTodos(state) {
return state.todos;
}
}
});
createApp(App).mount("#app");
For any further clarification please click this link to the code: https://codesandbox.io/s/stoic-keldysh-tjjhn?file=/src/App.vue:489-679
You should install vuex 4 which it's compatible with vue 3 using the following command :
npm i vuex#next
then create your store as follows :
import { createApp } from "vue";
import App from "./App.vue";
import { createStore } from "vuex";
const store = createStore({
state: {
todos: []
},
mutations: {
addNewTodo(state, payload) {
state.todos.push(payload);
}
},
actions: {},
getters: {
getTodos(state) {
return state.todos;
}
}
});
let app = createApp(App);
app.use(store);
app.mount("#app");

[Vue warn]: Computed property "axios" is already defined in Data. at <App>

I know similar questions already present in stackoverflow, but I still can't understand how to solve this. I am having the warning(look in the title) in my console.
You can reproduce the warning by the following code
//index.js
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'
import axios from 'axios';
const app = createApp(App)
app.__proto__.axios = axios
app.use(store)
app.mount("#app")
##App.vue
<template>
<div class="TodoList">
<p v-for="todo in todos" :key="todo.id">{{ todo.title }}</p>
</div>
</template>
<script>
export default {
mounted() {
this.$store.dispatch("fillItems");
},
computed: {
todos() {
return this.$store.getters.todos;
},
},
};
</script>
<style>
</style>
##store.js
import { createStore } from 'vuex';
export const store = createStore({
state: {
todos: []
},
getters: {
todos(state) {
return state.todos
}
},
mutations: {
FILL_ITEMS(state, payload) {
state.todos = payload
}
},
actions: {
fillItems({ commit }) {
this.axios
.get("https://jsonplaceholder.typicode.com/todos")
.then(res => commit('FILL_ITEMS', res.data))
}
}
})
You could add axios to app.config.globalProperties in order to access it inside any child component :
const app = createApp(App)
app.config.globalProperties.axios=axios
in child component use this.axios
but you couldn't access it inside the store context because this in the actions refers to the store instance, so you should import axios inside the store file and use it like :
import { createStore } from 'vuex';
import axios from 'axios';
export const store = createStore({
state: {
todos: []
},
getters: {
todos(state) {
return state.todos
}
},
mutations: {
FILL_ITEMS(state, payload) {
state.todos = payload
}
},
actions: {
fillItems({ commit }) {
axios
.get("https://jsonplaceholder.typicode.com/todos")
.then(res => commit('FILL_ITEMS', res.data))
}
}
})
or you could assign axios to the store instance (It's not recommended specially with typescript) :
const app = createApp(App)
store.axios = axios
app.use(store)
app.mount("#app")
In Vue 3, you can create app globals for components using provide/inject:
Providing
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'
import axios from 'axios';
const app = createApp(App)
app.provide('axios', axios); // Providing to all components here
app.use(store)
app.mount("#app")
Injecting
In the options API:
export default {
inject: ['axios']; // injecting in a component that wants it
}
In the composition API:
const { inject } = Vue;
...
setup() {
const axios = inject('axios'); // injecting in a component that wants it
}
Edit:
I answered too fast (thanks #BoussadjraBrahim), you're not asking about components, but I'll leave that answer too. If you just want to use axios in a separate module, you can use it like any import:
import axios from 'axios';
and use axios instead of this.axios

Vuex store is not accessible in external js files

The architecture of my app is attached here
Architecture of my app
So in my use case I just want to use getters in Axios interceptor (/../src/app/shared/services/http-client/http-client.js)so I can attach the authorization token in header but when I import store from /../src/app/app-state.js then it throws error as follow
Uncaught TypeError: Cannot read property 'getters' of undefined
at eval (vuex.esm.js?2f62:340)
at Array.forEach (<anonymous>)
at assertRawModule (vuex.esm.js?2f62:339)
at ModuleCollection.register (vuex.esm.js?2f62:244)
at eval (vuex.esm.js?2f62:258)
at eval (vuex.esm.js?2f62:123)
at Array.forEach (<anonymous>)
at forEachValue (vuex.esm.js?2f62:123)
at ModuleCollection.register (vuex.esm.js?2f62:257)
at new ModuleCollection (vuex.esm.js?2f62:218)
app-state.js (vuex store) /../src/app/app-state.js
import Vue from 'vue';
import Vuex from 'vuex';
import createPersistedState from "vuex-persistedstate";
import { appName } from '../environment/environment'
import { authState } from './auth'
import { organizationState } from "./organization"
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
authState,
organizationState
},
strict:false,
plugins: [
createPersistedState ({
key: appName,
})
]
});
main.js
import Vue from 'vue';
import './plugins';
import i18n from './plugins/i18n'
import './plugins/izitoast'
import App from './app/app.vue';
import DEFINES from './plugins/defines'
import './main.scss';
import router from './app/app-routes';
import store from './app/app-state';
import vuetify from './plugins/vuetify';
Vue.config.productionTip = false;
Vue.prototype.DEFINES = DEFINES;
new Vue({
router,
store,
vuetify,
i18n,
render: h => h(App)
}).$mount('#app');
**Organization module state (example of a module) **
import { list } from '../services'
import { getField, updateField } from 'vuex-map-fields';
/** Initial State */
const initialState = {
loading: false,
data: null,
error: null
}
/**
* Organization data mutations
*/
const mutations = {
updateField,
/** Organization data request */
ORGANIZATION_DATA_REQUEST(state,payload) {
Object.assign(state, {loading: true, data: payload})
},
/** Organization data success */
ORGANIZATION_DATA_SUCCESS(state, payload) {
Object.assign(state, {loading: false, data: payload})
},
/** Organization data error */
ORGANIZATION_DATA_ERROR(state) {
Object.assign(state, {
loading: false,
});
},
/** Organization data reset */
ORGANIZATION_DATA_RESET(state) {
Object.assign(state,...initialState)
}
}
const actions = {
async list(context){
// 1. Initiate request
context.commit('ORGANIZATION_DATA_REQUEST');
// 2. Get data from API and handle error
var response = await list().catch(error => {
context.commit('ORGANIZATION_DATA_ERROR')
throw error;
})
// 3. Set data in state
context.commit('ORGANIZATION_DATA_SUCCESS', response)
return response
}
}
const getters ={
getField,
getList: (state) => {
return state.data
},
}
export default {
namespaced: true,
mutations,
actions,
getters,
state: initialState
}
orjanization-state.js which combines all the feature states
import { organizationList } from "./shared/state"
export default {
modules: {
organizationList
}
}
well, if you want to get access to Vuex modules in a js file you can import vuex store and use it,
here is my code you can see I'm using Vuex actions in Axios:
import Store from "#/store/index";
import axios from "axios";
const userRole = localStorage.role ? `${localStorage.role}` : "";
let config = {
baseURL: process.env.VUE_APP_BASE_URL + userRole,
headers: {
Authorization: "Bearer " + localStorage.token
},
};
const _axios = axios.create(config);
// Add a response interceptor
_axios.interceptors.request.use(
function(config) {
Store.dispatch("loader/add", config.url);
return config;
},
function(error) {
Store.dispatch("loader/remove", error.response.config.url);
return Promise.reject(error);
}
);
_axios.interceptors.response.use(
function(response) {
// Do something with response data
Store.dispatch("loader/remove", response.config.url);
return response;
},
function(error) {
Store.dispatch("loader/remove", error.response.config.url);
return Promise.reject(error);
}
);
export const $http = _axios;
I hope this code will help you

Access Vuex mutators in navigation guards

I'm building an app with Laravel + VueJS. In order to restrict some routes, I use navigation guards. The problem is that I need to access Vuex mutators in order to know if the current user is logged in. The thing is that store is defined, but I cannot use the mutator from the router. I got this error: TypeError: Cannot read property 'commit' of undefined but as I said, store is well defined. Does someone have an idea ? Thanks !
routes
import Vue from 'vue'
import Router from 'vue-router'
import Hello from '#/components/Hello'
import Register from '#/components/Register'
import Login from '#/components/Login'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Hello',
component: Hello,
meta: {
showNavigation: true,
requireAuthentication: true
}
},
{
path: '/register',
component: Register,
meta: {
showNavigation: false,
requireAuthentication: false
}
},
{
path: '/login',
component: Login,
meta: {
showNavigation: false,
requireAuthentication: false
}
}
],
mode: 'history'
})
store
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
access_token: null,
expires_in: 0
},
mutations: {
setToken (state, response) {
state.access_token = response.body.access_token
state.expires_in = response.body.expires_in + Date.now()
},
getToken (state) {
if (!state.access_token || !state.expires_in) return null
if (state.expires_in < Date.now()) this.commit('destroyToken')
return state.access_token
},
destroyToken (state) {
state.access_token = null
state.expires_in = 0
},
isAuthenticated (state) {
return this.commit('getToken') !== null
}
},
actions: {
getOauthToken (context, user) {
var data = {
client_id: 2,
client_secret: 'XXXXXXXXXXXXXXXXXXXXXXXXXX',
grant_type: 'password',
username: user.email,
password: user.password
}
Vue.http.post('oauth/token', data)
.then(response => {
context.commit('setToken', response)
})
}
}
})
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import { store } from './store'
import VueResource from 'vue-resource'
import VeeValidate from 'vee-validate'
Vue.config.productionTip = false
Vue.use(VueResource)
Vue.use(VeeValidate)
Vue.http.options.root = 'http://codex.app'
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requireAuthentication)) {
console.log(store)
console.log(store.commit('isAuthenticated'))
if (!store.commit('isAuthenticated')) {
next('/login')
} else {
next()
}
} else {
next()
}
})
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
when we commit mutations, it refers to change the state and ONLY the state.when you need more complex mutations to change the state, using actions instead.

Categories