How to re-direct to login page from vuex action in vue - vue.js

Hi i have a existing project where in all action.js we are dispatching logoutFromServer upon 401 Unauthorized
here is how it will look like this
users.action.js
async getAllUsers(context,payload={}){
try{
let resp = await axios.get(...);
}catch(error){
if(error.response.status == 401)
context.dispatch('logoutFromServer');
}
}
customers.action.js
async getAllCustomers(context,payload={}){
try{
let resp = await axios.get(...);
}catch(error){
if(error.response.status == 401)
context.dispatch('logoutFromServer');
}
}
there above code is repeated in almost every GET,POST,PUT,DELETE at least more than 1000 places(so i cannot change them now). upon dispatch of logoutFromServer i'm getting
TypeError: Cannot read properties of undefined (reading 'push')
for below code
authentication.action.js
async logoutFromServer(context){
try{
let resp = await axios.delete(...);
}catch(e){
console.log('must be already deleted');
}
//clear cookie
this.$router.push({name:"login"}) <-- here the above error occurs i,e `TypeError: Cannot read properties of undefined (reading 'push')`
}
Question: how to re-direct from vuex actions to /login route
Please help me thanks in advance !!

you can explicitly import the router and use it.
==> router.js
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
export const router = new VueRouter({
mode: 'hash',
base: "./",
routes: [
{ path: "/", component: home},
...
]
})
===> actions.js
import {router} from "../router.js"
export const someAction = ({commit}) => {
router.push("/");
}

Related

(vue) Uncaught InternalError: too much recursion: failed to use axios

I met a weird problem, when I set up main.js and run the server, it shows me this error
and here is my code in main.js,
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementPlus from 'element-plus';
import axios from 'axios'
import 'element-plus/lib/theme-chalk/index.css';
axios.defaults.baseURL = 'http://localhost:8000'
const app = createApp(App)
app.use(store)
app.use(router)
app.use(ElementPlus)
console.log('1111')
app.use(axios)
console.log('aaa')
app.mount('#app')
I set a console.log to track the error, the '1111' shows but 'aaa' never shows, so I can only know the error occurs in the line app.use(axios), that's so confusing, did anyone ever met this problem? How to solve it?
Here you have 2 options:
If you're using Vue3, import it in your Vue, like you did, but as written in this awnser, i think you should do something more like that:
import { createApp } from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
const app = createApp(App)
app.use(VueAxios, axios) // πŸ‘ˆ
app.mount('#app')
You wanna use only axios and don't get bothered by it, then you don't have to use VueAxios or make something like app.use(axios) in your main.js, and then make a httpRequestsService file which will handle all the axios part.
For example, here is mine:
import axios from "axios";
axios.defaults.baseURL = process.env.API_BASE_URL
axios.interceptors.response.use(res => res, err => {
// some code if you wanna handle specific errors
throw err;
}
);
const getHeader = (method, endpoint, header, body) => {
const publicEndpoints = [
{
method: 'POST',
endpoint: '/auth/local'
}
]
const publicEndpoint = publicEndpoints.find(e => e.method === method && e.endpoint === endpoint)
if (!publicEndpoint) {
header.Authorization = localStorage.getItem("jwt")
}
return body ? { headers: header, data: body } : { headers: header }
}
export default {
get(endpoint, header = {}) {
return axios.get(endpoint, getHeader('GET', endpoint, header))
},
post(endpoint, body = {}, header = {}) {
console.log(body)
return axios.post(endpoint, body, getHeader('POST', endpoint, header))
},
put(endpoint, body = {}, header = {}) {
return axios.put(endpoint, body, getHeader('PUT', endpoint, header))
},
delete(endpoint, body = {}, header = {}) {
return axios.delete(endpoint, getHeader('DELETE', endpoint, header, body))
},
}

Nativescript Vue - async operations in main.js before rendering

i would like to add a login page to my app with Firebase Authentication:
https://github.com/EddyVerbruggen/nativescript-plugin-firebase/blob/master/docs/AUTHENTICATION.md
Following the guide i've added the "onAuthStateChanged" function inside the init firebase.
Now i would like to pass to the render function in the Vue instance creation the correct page, based on the value returned by the Firebase function.
If the user it's authenticated, will be rendered the "home.vue" page, otherwise the "login.vue" page.
The problem it's that the firebase function return the state of the user after the Vue instance creation.
Here my code:
import Vue from 'nativescript-vue'
import store from './store'
import Home from './components/Page/home.vue'
import Login from './components/Page/login.vue'
import VueDevtools from 'nativescript-vue-devtools'
var firebase = require("#nativescript/firebase").firebase;
var pageToRender;
firebase.init({
onAuthStateChanged: function(data) {
if (data.loggedIn) {
pageToRender = Home;
}else{
pageToRender = Login;
}
}
}).then(
function () {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
if(TNS_ENV !== 'production') {
Vue.use(VueDevtools)
}
// Prints Vue logs when --env.production is *NOT* set while building
Vue.config.silent = (TNS_ENV === 'production')
new Vue({
store,
render: h => h('frame', [h(pageToRender)])
}).$start()
I already tried to move all the code inside an async function in order to await the firebase response before the Vue instance creation but i receive the error:
System.err: Error: Main entry is missing. App cannot be started.
Verify app bootstrap.
In this way:
async function start(){
var loggedIn = await firebase_start();
var pageToRender;
if (loggedIn) {
pageToRender = Home;
}else{
pageToRender = Login;
}
if(TNS_ENV !== 'production') {
Vue.use(VueDevtools)
}
// Prints Vue logs when --env.production is *NOT* set while building
Vue.config.silent = (TNS_ENV === 'production')
new Vue({
store,
render: h => h('frame', [h(pageToRender)])
}).$start()
}
Thanks for the help!
I would approach it differently.
When the user logs in, you can save this on the device with the https://github.com/nativescript-community/preferences plugin as follows:
function successLoginUser(){
const prefs = new Preferences();
prefs.setValue("isUserLogin", true);
}
And then when starting the application you can do something like this:
import Vue from 'nativescript-vue'
import store from './store'
import Home from './components/Page/home.vue'
import Login from './components/Page/login.vue'
import VueDevtools from 'nativescript-vue-devtools'
//This
const prefs = new Preferences();
const pageToRender = prefs.getValue("isUserLogin") ? Home: Login ;
if(TNS_ENV !== 'production') {
Vue.use(VueDevtools)
}
// Prints Vue logs when --env.production is *NOT* set while building
Vue.config.silent = (TNS_ENV === 'production')
new Vue({
store,
render: h => h('frame', [h(pageToRender)])
}).$start()
When the user logs out:
const prefs = new Preferences();
prefs.setValue("isUserLogin", false);
I haven't tried it but it should work
//for me here firebase is imported from a diffrent file where i already initialized it.
let app;
firebase.auth().onAuthStateChanged((user, error) => {
//decide which page to render
//also make sure both components you are trying to render are imported
if (!app) {
app = new Vue({
router,
store,
render: (h) => h(PageToRender),
}).$start()
}
});

vuex persisted state not working with vue router navigation guard

I've added vuex-persistedstate as defined in documentation. (confirmed and working)
export default new Vuex.Store({
modules: {
auth,
},
plugins: [VuexPersistedState()]
});
I've set up a router navigation guard to redirect to home page on login
/* Auth based route handler */
router.beforeEach((to, from, next) => {
if (to.meta.hasOwnProperty('requiresAuth')) {
if (to.meta.requiresAuth === true) {
let isAuthenticated = authStore.getters.isAuthenticated
if (isAuthenticated(authStore.state) === true) {
next()
} else {
next({name: 'login'})
}
} else {
let isAuthenticated = authStore.getters.isAuthenticated
console.log(authStore.state)
if (isAuthenticated(authStore.state) === true) {
next({name: 'home'})
} else {
next()
}
}
} else {
next()
}
})
The vuex persistedstate restores store from local storage but not before navigation guard!
I can provide any necessary part of the source code for evaluation. Please comment your request as needed. Experimental solutions are also welcome. This is just a personal training application for me!
Help is appreciated!
I know this is probably of no use to #Vaishnav, but as I wen't down the same rabbit hole recently I figured I'd post a workaround I found for this here as this was the only post I found that asked this issue specifically.
in your store/index.js You need to export both the function and the store object.
Change this :
export default() => {
return new vuex.Store({
namespaced: true,
strict: debug,
modules: {
someMod
},
plugins: [createPersistedState(persistedStateConfig)]
});
};
to:
const storeObj = new vuex.Store({
namespaced: true,
strict: debug,
modules: {
someMod
},
plugins: [createPersistedState(persistedStateConfig)]
});
const store = () => {
return storeObj;
};
export {store, storeObj}
Then also, as you have now changed the way you export the store you will also need to change the way it's imported.
Everywhere in your app you've imported the store ie: in main.js -> import store from '#/store' excluding the router.js you will need to change to import {store} from '#/store'
And in your router.js just import {storeObj} from '#/store' and use that instead of store in your router guard ie: storeObj.getters['someMod/someValue']
I found a working example.
As the router isn't a component.
In router config file -
import authStore from '#/stores/auth/store'
Instead -
import store from '#/store'
in navigation guard, I replaced
let isAuthenticated = authStore.getters.isAuthenticated
if(isAuthenticated(authstore.state) === true){...}
with -
let isAuthenticated = store.getters['auth/isAuthenticated']
if(isAuthenticated === true){...}
And now this works like charm...

How to use axios in Vue2 project created with vue-cli3

I created a new vue project using the command vue create axe using vue-cli-3.0.016beta. Then installed axios using npm install axios --save. In the main.js file I imported axios as shown below.
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
Vue.config.productionTip = false
Vue.use(axios)
new Vue({
render: h => h(App)
}).$mount('#app')
There is not a bit of code change other than this. Still I get an error like the following:
Unhandled promise rejection
TypeError
​
columnNumber: 7
​
fileName: "http://localhost:8080/app.js line 1065 > eval"
​
lineNumber: 57
​
message: "parsed is undefined"
​
stack: "isURLSameOrigin#webpack-internal:///./node_modules/axios/lib/helpers/isURLSameOrigin.js:57:7\ndispatchXhrRequest#webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:109:50\nPromise#webpack-internal:///./node_modules/core-js/modules/es6.promise.js:177:7\nxhrAdapter#webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:12:10\ndispatchRequest#webpack-internal:///./node_modules/axios/lib/core/dispatchRequest.js:59:10\nrun#webpack-internal:///./node_modules/core-js/modules/es6.promise.js:75:22\nnotify/<#webpack-internal:///./node_modules/core-js/modules/es6.promise.js:92:30\nflush#webpack-internal:///./node_modules/core-js/modules/_microtask.js:18:9\n"
​
__proto__: Object { stack: "", … }
I want to axios globally to use interceptors, hence calling it here in main.js. But if I use it in a view-page there is no error!
is this a bug or I'm doing it wrong? Kindly help me to fix this and use axios globally.
Thanks
so the error I see is here
Vue.use(axios)
Vue.use expects a vue installable plugin.
You could have a look at vue-axios
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
but I would highly discourage it.
It's best to create your own ApiHandler.js file that handles all the remote stuff separately, and you can easily call from anywhere including vue components and vuex.
here is the beginning of my class
<script>
import axios from 'axios';
class ApiHandler{
constructor(apiUrl) {
this.axios = axios;
this.apiUrl = apiUrl || ''; // this line allow passing a custom endpoint for testing
this.config = {
headers: { 'Cache-Control': 'no-cache' }, // can setup to prevent all caching
baseURL: this.apiUrl,
};
}
/**
* #param {Object} payload
* #param {String} payload.username
* #param {String} payload.password
*/
login({ username, password }) {
return new Promise((resolve, reject) => {
this.axios.post('/api/login', { username: username.toLowerCase(), password }, this.config)
.then((response) => {
if (response.code === 200 && response.body && response.body.token) {
resolve(response.body.token);
} else {
reject('Bad Login');
}
})
.catch((err) => {
reject('internal error');
});
});
}
}
</script>
you can then call this from anywhere by...
<script>
import ApiHandler from '../lib/ApiHandler';
const apiRequest = new ApiRequest();
// and then anywhere in the script
let payload = {
username:'someuser',
password:'somepassword',
};
apiRequest.login(payload)
.then(()=>{
// yay - I'm logged in
})
.catch(err => {
// oh oh, display error
})
</script>
this gives you much more flexibility and allows you to separate the remote actions and allows doing first-leg response handling separate of your component, which allows more re-usability.
instead of
Vue.use(axios);
you should
Vue.prototype.$axios = axios;
then you can use it globally
login() {
this.$axios.post('<host>/api/login', data)
.then((res) => { // dosomething })
.catch((err) => { // dosomething });
}
if you want to add globally interceptors with axios, you can
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
// and
Vue.prototype.$axios = axios;

How to attach axios / axios interceptor to Nuxt globally ?

how would i go about attaching axios / axios interceptor globally to nuxt (so its available everywhere), same how i18n is attached ?
The idea is that i would like to have a global axios interceptor that every single request goes through that interceptor.
Thanks
you can create a plugin called axios (/plugins/axios.js)
import Vue from 'vue';
import axios from 'axios';
axios.interceptors.request.use((config) => {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
Vue.use(axios);
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
thats all, your interceptor is now working globally
It's hidden in the documentation - https://nuxtjs.org/docs/2.x/directory-structure/plugins
See number 3 of the first photo:
// plugins/axios.js
export default function ({ $axios, redirect }) {
$axios.onError(error => {
if (error.response.status == 404) {
redirect('/sorry')
}
})
}
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
Maybe will be helpful for someone.
It just sets the lang parameter for every request.
Π‘reate a plugin called axios (/plugins/axios.js). Put it there:
export default function ({ $axios, app, redirect }) {
$axios.onRequest(config => {
config.params = config.params || {}; // get existing parameters
config.params['lang'] = app.i18n.locale;
})
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
})
}
Add in nuxt.config.js:
module.exports = {
plugins: [
'~/plugins/axios'
]
};
Create a new module, call it request.js for example.
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://example.org' // if you have one
})
// Put all interceptors on this instance
instance.interceptors.response.use(r => r)
export default instance
Then simply import that instance whenever you need it and use it like it was a normal axios instance:
import request from './request'
await request.get('/endpoint')
// or use promises
request.get('/endpoint').then(data => data)
If you really need it globally you can use the following code in your entry point of the application:
import request from './request'
global.request = request
// use it:
await request.get('example.org')
Or you can add it to the vue protype
Vue.prototype.$request = request
// in your component:
this.$request.get()
I'd advice against it though.