Access to main Vue instance in App.vue script - javascript

In Vue2, I'm trying to set up an axios interceptor in my App.vue file to trap any responses that come back 401 from my API so I can redirect the user to the Vue route /sign-in. My code works, but I rely on storing the Vue instance created in main.js in window.appvue.
In main.js:
window.appvue = new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
In App.vue:
<script>
import...
export default { ... }
export const $axios = axios.create();
$axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response.status === 401) {
window.appvue.$router.push("/sign-in");
} else {
return Promise.reject(error);
}
}
);
<script>
I have tried importing $axios from App.vue in main.js and moving the $axios.interceptors.response.use(...) code to that file, but then the interceptor never runs when I have a page where an $axios.get() returns 401.
Is there a way to accomplish this without storing the main Vue instance in window as a global? Or should I just go with what's working and call it a day?
P.S.
I was not aware of the existence of $root, when I asked this question, and I have not tried a version where the code in App.vue uses $root instead of relying on the window.appvue global, but when it comes to accessing the main/root instance of Vue from main.js, $root is definitely preferable to a global.

I managed to avoid using the global by isolating the axios wrapper into its own module/file. Then my .vue files that need axios can import from that instead of from axios directly
In {root}/src/axios.js:
import axios from "axios";
import router from "#/router";
const $axios = axios.create();
$axios.interceptors.response.use(
[stuff that uses router]
);
export default $axios;
Then in my files that need to call to the API:
import $axios from "#/axios.js";
and use $axios instead of axios to make the API call.

inside your main.js try something like this
import router from './router'
import axios from 'axios'
Vue.prototype.axios.interceptors.reponse.use(
res => {
// res stuff here
},
err => {
// error stuff here
if (err.response.status === 401) {
router.push("/sign-in");
} else {
return Promise.reject(err);
}
}
)

Related

Vue 3's Provide / Inject using the Options API

I've been trying to follow the documentation for the API on the Vue 3 website which says to use app.provide('keyName',variable) inside your main.js file like so:
import App from './App.vue'
import { createApp } from 'vue'
import axios from 'axios'
const app = createApp(App)
app.provide('axios', axios)
app.use('Vue')
app.mount('#app')
Then inject and use it in your child component like so:
export default {
inject: ['axios'],
...
createUser (data) {
return this.axios.post('/users', data)
}
}
However doing so just gives me this error in my console:
Uncaught TypeError: Cannot read properties of undefined (reading 'post')
Is there anything I'm missing? I didn't see any about an import unless you're using the Composition API. Can provide / inject be called from within a .js file? I would expect so as long as its within a export default {} statement
Ive tried following the API to a "T" but it simply refuses to work for me. Also tried searching the web for solutions but everything I've found says what I'm doing should be working just fine.
It works, see the playground.
But is not absolutely necessary, since with the browser library version axios is globally defined and could be accessed also without inject
You could also save yourself some time with the vue-axios plugin.
Example
const { createApp } = Vue;
const myComponent = {
inject: ['axios'],
created() {
this.axios.get('/')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
},
template: '<div>My Component</div>'
}
const App = {
components: {
myComponent
}
}
const app = createApp(App)
app.provide('axios', axios)
app.mount('#app')
<div id="app">
<my-component></my-component>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>
<script src="https://unpkg.com/axios#1.3.1/dist/axios.min.js"></script>

How to properly install a Pinia Store?

I'm building a Vue 3 app using the OptionsAPI along with a Pinia Store but I frequently run into an issue stating that I'm trying to access the store before createPinia() is called.
I've been following the documentation to use the Pinia store outside components as well, but maybe I'm not doing something the proper way.
Situation is as follows:
I have a login screen (/login) where I have a Cognito session manager, I click a link, go through Cognito's signup process, and then get redirected to a home route (/), in this route I also have a subroute that shows a Dashboard component where I make an API call.
On the Home component I call the store using useMainStore() and then update the state with information that came on the URL once I got redirected from Cognito, and then I want to use some of the state information in the API calls inside Dashboard.
This is my Home component, which works fine by itself, due to having const store = useMainStore(); inside the mounted() hook which I imagine is always called after the Pinia instance is created.
<template>
<div class="home">
<router-view></router-view>
</div>
</template>
<script>
import {useMainStore} from '../store/index'
export default {
name: 'Home',
components: {
},
mounted() {
const store = useMainStore();
const paramValues = {}
const payload = {
// I construct an object with the properties I need from paramValues
}
store.updateTokens(payload); // I save the values in the store
},
}
</script>
Now this is my Dashboard component:
<script>
import axios from 'axios'
import {useMainStore} from '../store/index'
const store = useMainStore();
export default {
name: "Dashboard",
data() {
return {
user_data: null,
}
},
mounted() {
axios({
url: 'myAPIUrl',
headers: { 'Authorization': `${store.token_type} ${store.access_token}`}
}).then(response => {
this.user_data = response.data;
}).catch(error => {
console.log(error);
})
},
}
</script>
The above component will fail, and throw an error stating that I'm trying to access the store before the instance is created, I can solve this just by moving the store declaration inside the mounted() hook as before, but what if I want to use the store in other ways inside the component and not just in the mounted hook? And also, why is this failing? By this point, since the Home component already had access to the store, shouldn't the Dashboard component, which is inside a child route inside Home have the store instance already created?
This is my main.js file where I call the createPinia() method.
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const pinia = createPinia();
createApp(App).use(router).use(pinia).mount('#app')
And the error I get is:
Uncaught Error: [🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?
My Store file:
import { defineStore } from 'pinia';
export const useMainStore = defineStore('main', {
state: () => ({
access_token: sessionStorage.getItem('access_token') || '',
id_token: sessionStorage.getItem('id_token') || '',
token_type: sessionStorage.getItem('token_type') || '',
isAuthenticated: sessionStorage.getItem('isAuthenticated') || false,
userData: JSON.parse(sessionStorage.getItem('userData')) || undefined
}),
actions: {
updateTokens(payload) {
this.id_token = payload.id_token;
this.access_token = payload.access_token;
this.token_type = payload.token_type
sessionStorage.setItem('id_token', payload.id_token);
sessionStorage.setItem('access_token', payload.access_token);
sessionStorage.setItem('token_type', payload.token_type);
sessionStorage.setItem('isAuthenticated', payload.isAuthenticated);
},
setUserData(payload) {
this.userData = payload;
sessionStorage.setItem('userData', JSON.stringify(payload));
},
resetState() {
this.$reset();
}
},
})
It's possible but not common and not always allowed to use use composition functions outside a component. A function can rely on component instance or a specific order of execution, and current problem can happen when it's not respected.
It's necessary to create Pinia instance before it can be used. const store = useMainStore() is evaluated when Dashboard.vue is imported, which always happen before createPinia().
In case of options API it can be assigned as a part of component instance (Vue 3 only):
data() {
return { store: useMainStore() }
},
Or exposed as global property (Vue 3 only):
const pinia = createPinia();
const app = createApp(App).use(router).use(pinia);
app.config.globalProperties.mainStore = useMainStore();
app.mount('#app');
Since you're using Vue 3, I suggest you to use the new script setup syntax:
<script setup>
import { reactive, onMounted } from 'vue'
import axios from 'axios'
import { useMainStore } from '../store'
const store = useMainStore();
const data = reactive({
user_data: null
})
onMounted (async () => {
try {
const {data: MyResponse} = await axios({
method: "YOUR METHOD",
url: 'myAPIUrl',
headers: { 'Authorization': `${store.token_type} ${store.access_token}`}
})
data.user_data = MyResponse
} catch(error){
console.log(error)
}
})
</script>
Using setup you can define that store variable and use it through your code.
everyone after a lot of research I found the answer to this issue,
you must pass index.ts/js for const like below:
<script lang="ts" setup>
import store from '../stores/index';
import { useCounterStore } from '../stores/counter';
const counterStore = useCounterStore(store());
counterStore.increment();
console.log(counterStore.count);
</script>

cannot import electron in react componment for ipcRenderer

So I am trying to import the ipcRenderer into a react component to communicate with the electron side. The issue is I cannot import electron. I tried
import { ipcRenderer } from 'electron/renderer'
returns module electron/renderer not found
import { ipcRenderer } from 'electron'
returns fs.existsSync is not a function
const renderer = require('electron');
returns fs.existsSync is not a function
const renderer = require('electron').ipcRenderer;
returns fs.existsSync is not a function
const renderer = window.require('electron');
returns window.require is not a function
I do not know what to do anymore, I have tried everything
I got it! using electron-react-bolierplate they prepared a custom preload.js script that exposes three functions to the rendered components: myPing: (Just a demo, send a ping message to the console) and exposes the on and once ipcRenderer methods
import { Component } from 'react';
...
...
class Hello extends Component {
componentDidMount() {
console.log('Mounted!', 'Window', window, 'electronApi', window.electron, 'ipcRenderer', window.electron.ipcRenderer);
window.electron.ipcRenderer.on('ipc-example', (arg) => {
// eslint-disable-next-line no-console
console.log(arg);
});
window.electron.ipcRenderer.myPing();
}
render() {
return (
<div>
...
...
I used to have same problem. You should not import electron directly inside your renderer/react component. Instead in your preload.ts file you are given some basic configuration by electron-react-bolierplate to use.
So inside your react component you should use window.electron.ipcRenderer.on('channel-name', args) sample below
const myEventHandler = () => {
window.electron.ipcRenderer.on('channel-name', (event, data) => {
console.log('data, event', data, event);
});
};
window.electron, here electron is the name given in preload.ts file. contextBridge.exposeInMainWorld('electron', {...})

Vue Router push Error: Avoided redundant navigation to current location

Is there a way to avoid Error: Avoided redundant navigation to current location. I need to do a pagination, this is the method:
handlePageChange(page: number): void {
const query = {
...this.$route.query,
page: page.toString(),
};
this.$router.push({ name: 'PageName', query });
}
and I keep getting error in the console:
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/page-path?page=2".
I tried doing a catch with the router but that does not work. Can someone help me shed some light what am i doing wrong here? :/
If it's safe to ignore, and you are using vue-router ^3.4.0, you can do:
import VueRouter from 'vue-router'
const { isNavigationFailure, NavigationFailureType } = VueRouter
...
this.$router.push(fullPath).catch(error => {
if (!isNavigationFailure(error, NavigationFailureType.duplicated)) {
throw Error(error)
}
})
For more details, please refer to Navigation Failures.
You can globally handle this problem.
Open your router's index file (index.js) and use this code inside it-
import VueRouter from "vue-router";
Vue.use(VueRouter);
// Handle navigation duplication for router push (Globally)
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch((error) => {
});
};
Try this example - here
Cheers!
This is a bit dated, but merging both existing answers for a cleaner, global handling:
import VueRouter from "vue-router";
Vue.use(VueRouter);
const { isNavigationFailure, NavigationFailureType } = VueRouter;
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
original_push.call(this, location).catch(error => {
if(!isNavigationFailure(error, NavigationFailureType.duplicated)) {
throw Error(error)
}
})
};

accessing vuex store in js file

Just like in main.js, I'm trying to access my store from a helper function file:
import store from '../store'
let auth = store.getters.config.urls.auth
But it logs an error:
Uncaught TypeError: Cannot read property 'getters' of undefined.
I have tried
this.$store.getters.config.urls.auth
Same result.
store:
//Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
config: 'config',
},
getters: {
config: state => state.config
},
});
export default store
How do I make my store available outside of components?
The following worked for me:
import store from '../store'
store.getters.config
// => 'config'
This Worked For Me In 2021
I tried a bunch of different things and it seems, at least in Vue 3, that this works. Here is an example store:
export default {
user: {
bearerToken: 'initial',
},
};
Here is my Getters file:
export default {
token: (state) => () => state.user.bearerToken,
};
Inside your .js file add the page to your store\index.js file.
import store from '../store';
In order to access the getters just remember it is a function (which may seem different when you use mapGetters.)
console.log('Checking the getters:', store.getters.token());
The state is more direct:
console.log('Checking the state:', store.state.user.bearerToken);
If you are using namespaced modules, you might encounter the same difficulties I had while trying to retrieve items from the store;
what might work out for you is to specify the namespace while calling the getters (example bellow)
import store from '../your-path-to-your-store-file/store.js'
console.log(store.getters.['module/module_getter']);
// for instance
console.log(store.getters.['auth/data']);
put brackets on your import and it should work
import { store } from '../store'
using this approach has worked for me:
// app.js
import store from "./store/index"
const app = new Vue({
el: '#app',
store, //vuex
});
window.App = app;
// inside your helper method
window.App.$store.commit("commitName" , value);
if you are using nuxt you can use this approach
window.$nuxt.$store.getters.myVar
if you have multiple modules
window.$nuxt.$store.getters['myModule/myVar']
export default ( { store } ) => {
store.getters...
}

Categories

Resources