Vuex store is not accessible in external js files - javascript

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

Related

[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

How do I set up GraphQL for my vuejs 3 app

I am new to VueJS and I have been trying to setup graphql with my vuejs but I can't seem to get it right.
Funny thing is there's no error on my console apart from a
[Vue warn]: A plugin must either be a function or an object with an "install" function. error.
And this only comes up when I instantiate vueapollo to the use method.
Here's my main.js file
import { createApp } from 'vue';
import ElementUI from 'element-plus';
import ApolloClient from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import VueApollo from 'vue-apollo';
import App from './App.vue';
import router from './router';
import store from './store';
import './assets/style/theme/index.css';
// HTTP connection to the API
const httpLink = createHttpLink({
// You should use an absolute URL here
uri: 'http://localhost:4999/graphql',
});
// Cache implementation
const cache = new InMemoryCache();
// Create the apollo client
const apolloClient = new ApolloClient({
link: httpLink,
cache,
});
/* The provider holds the Apollo client instances
that can then be used by all the child components. */
// eslint-disable-next-line
const apolloProvider = new VueApollo({
defaultClient: apolloClient,
});
createApp(App)
.use(ElementUI)
.use(apolloClient)
.use(store)
.use(router)
.mount('#app');
I have a page file where presently, I want to reach the "Hello" endpoint I created earlier
<template>
<div>
<h1>Hello</h1>
<h1>{{ hello }}</h1>
</div>
</template>
<script>
import gql from 'graphql-tag';
export default {
data() {
return {
hello: '',
};
},
apollo: {
// Simple query that will update the 'hello' vue property
hello: gql`
query {
hello
}
`,
},
};
</script>
I also struggled with it a couple of weeks ago.
I don't remember how, but I got it to work.
I post my code here, in case it helps anyone in the future.
The difference between the question author and my code is how I pass the Apollo client to the create-app setup function. I don't know if it is correct, but it works.
PS: I am also using TypeScript on top of everything.
My apollo-client.ts file
import { ApolloError } from '#apollo/client';
import { ApolloClient, InMemoryCache, HttpLink } from '#apollo/client/core';
import { ErrorResponse } from '#apollo/client/link/error';
import { onError } from '#apollo/client/link/error';
import { logErrorMessages } from '#vue/apollo-util';
function getHeaders() {
const headers = {
'content-type': 'application/json',
};
/* const token = window.localStorage.getItem('apollo-token');
if (token) {
headers['Authorization'] = `Bearer ${token}`;
} */
return headers;
}
// Create an http link:
const httpLink = new HttpLink({
uri: 'http://localhost:3000/query',
fetch: (uri: RequestInfo, options: RequestInit) => {
options.headers = getHeaders();
return fetch(uri, options);
},
});
const errorLink = onError((error: ErrorResponse | ApolloError) => {
if (process.env.NODE_ENV !== 'production') {
logErrorMessages(error);
}
});
// Create the apollo client
export const apolloClient = new ApolloClient({
cache: new InMemoryCache(),
connectToDevTools: true,
link: errorLink.concat(httpLink),
});
My main.ts file:
import { apolloClient } from './apollo-client';
import { createApp, provide, h } from 'vue';
import { DefaultApolloClient } from '#vue/apollo-composable';
import { createApolloProvider } from '#vue/apollo-option';
import { loadFonts } from './plugins/webfontloader';
import App from './App.vue';
import router from './router';
import store from './store';
import vuetify from './plugins/vuetify';
// TODO: Auth
// import authPlugin from "./auth/authPlugin"
// Create a provider
const apolloProvider = createApolloProvider({
defaultClient: apolloClient,
});
loadFonts();
const app = createApp({
setup() {
provide(DefaultApolloClient, apolloClient);
},
render: () => h(App),
});
app.use(router).use(store).use(vuetify).use(apolloProvider).mount('#app');

Making axios global in Vue project is not working

Until now, I had been importing axios in each Vue component where I wanted to make HTTP requests, like this.
<script lang="ts">
import axios from 'axios';
#Component
export default class ExamplePage extends Vue {
created(): void {
axios.post(some_path);
}
}
However, now I want to define a global interceptor for all axios requests, basically to catch all 401 unauthorized responses from the backend server (Rails) and log out the user.
My understanding so far is that you must instantiate axios once and use it everywhere, instead of importing and using a different instance in each file.
I've referred to this and this, and tried the below.
// application.js
import '../assets/sass/base.sass';
import App from '../app';
import Vue from 'vue';
import VueRouter from 'vue-router';
import axios from 'axios';
import router from '../routes';
Vue.use(VueRouter);
document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: '#application',
router,
render: (h) => h(App),
});
});
axios.interceptors.response.use(
response => response,
error => {
const status = error.response;
if(status === 401) {
// do stuff
}
}
);
Vue.prototype.$http = axios
When I tried to call this.$http.put(...) in another file, it said property $http doesn't exist (I'm guessing it's because this in the context of that component is the component itself, but I'm not sure). How can I fix this?
[UPDATE] Thanks to the responses, I decided to initialize an axios instance in a separate file and then use that instead. However, this is still not working. The 401 responses don't seem to be triggering the interceptor at all.
Is there some additional configuration necessary?
// axios-instance.ts
import axios, { AxiosInstance } from 'axios';
const axiosInstance: AxiosInstance = axios.create();
axiosInstance.interceptors.response.use(
response => response.data,
async function(error) {
console.log("THIS IS THE 401 INTERCEPTOR")
const status = error.response;
if(status === 401) {
// Log out user
}
}
);
export default axiosInstance;
// Some component
<script lang="ts">
import axiosInstance from 'axios-instance';
#Component
export default class ExamplePage extends Vue {
created(): void {
axiosInstance.post(some_path);
}
}
Doesn't solve your question directly, but the way I create instance of axios is:
// axios-instance.js
import axios from 'axios'
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
export default instance
After you just import the instance
// application.js
import axios from '../../axios-instance'
You can't use this when you aren't in the vue instance.
Try this:
// application.js
import '../assets/sass/base.sass';
import App from '../app';
import Vue from 'vue';
import VueRouter from 'vue-router';
import axios from 'axios';
import router from '../routes';
Vue.use(VueRouter);
axios.interceptors.response.use(
response => response,
error => {
const status = error.response;
if(status === 401) {
// do stuff
}
}
}
);
Vue.prototype.$http = axios;
const VueInstance = new Vue({
el: '#application',
router,
render: (h) => h(App),
});
Now you can make HTTP request using this.$http:
<script>
export default {
methods: {
doStuff() {
this.$http.post('path_to_post_request').then(({ data }) => {
// do something with received data
}).catch((e) => {});
}
}
}
</script>
I had the same problem.
It is not so clear in Axios API docs that error responses should be intercepted in the response interceptor's error callback.
It should be done like this:
axios.interceptors.response.use(function (response) {
//This is the success callback in case u ever need it
return response;
}, function (error) {
// This is the callback I was talking about.
// Do something with request error
// Tip: error.response is the actual response with error code
return Promise.reject(error);
})
This can be seen in the Axios API Documentation - Interceptors section
I recommend that you implement the axios instance in a separate js file:
// utils/request.js
import axios from 'axios'
const service = axios.create({
baseURL: '',
headers: {},
withCredentials: true,
// ...other options
})
// request interceptor
service.interceptors.request.use(config => {
// ...
return config
}, err => {
return Promise.reject(err)
})
// response interceptor
service.interceptors.response.use(response => {
// ...
return response
}, err => {
return Promise.reject(err)
})
export default service
And then you can using http request like this:
import request from '#/utils/request'
request({
url: '',
method: 'post',
data: {}
}).then(res => {
// Do something after the request is successful
}).catch(err => {
// Do something after the request fails
})
Try implementing a service:
src / services / yourservice.js
import axios from 'axios'
const apiClient = axios.create({
baseURL: 'https://example.com/api',
withCredentials: false,
timeout: 1000,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa('api_username_here' + ':' + 'api_password_here')
}
})
export default {
getOrders(){
return apiClient.get('/orders?status=processing')
},
getProducts(id){
return apiClient.get('/products/' + id)
},
}
Use the service in a Component:
src / component / yourcomponent.vue
import YourService from '#/services/YourService.js';
data() {
return {
orders: null, //Typescript
}
},
created() {
yourservice.getOrder(id).then(response => {
this.orders = response.data
}).catch(error => {
console.log(error)
});
}

How to access Vuex store in helper functions?

For vue-axios auth by api_token i use helper file api.js.
i got error - Uncaught TypeError: Cannot read property 'getters' of undefined.
I think api.js helper does not see global storage - Vuex $store.
In other components i do not need import Vuex storage, he avalaible in any place of app.
How use this.$storage in helper?
//api.js
import axios from 'axios'
let api_token = this.$store.getters.get_api_token //got error!
export function get(url) {
return axios({
method: 'GET',
url: url,
headers: {
'Authorization': `Bearer ${api_token}`
}
})
}
//Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
api_token: 'vnbnvnvnvb',
},
getters: {
get_api_token(state){
return state.api_token
}
},
});
export default store
//App.vue
import {get} from './helpers/api';
export default {
created() {
get(`/api/user/${1}`)
.then((res) => {
///do it
})
.catch((err) => {
console.log(err);
})
}
}
Found answer
// some JS file
import store from './../store'; // path to your Vuex store
let user = store.getters.user;
// do stuff with user
https://laracasts.com/discuss/channels/vue/vuex-accessing-vuex-outside-of-a-vue-component

Vue.js & Firebase 'User' info

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

Categories