Vuejs 3 Router Cannot read property 'push' of undefined - vue.js

I am currently trying to start a Vue app which will contain a user login.
For some reason the I have been struggling with getting this router redirect to work.
The implementation is straight from the vueschool page and specifically aimed at the composition API.
What am I missing here?
Every time I run the registration script it registers the user correctly and logs the error that it can't find the 'push' property on of undefined.
My code completion is telling me it's there, the linting works fine and the IDE Webstorm isn't giving any errors.
<script>
import firebase from "firebase/app";
import "firebase/auth";
import { defineComponent, ref } from "vue";
import { useRouter } from "vue-router";
export default defineComponent({
name: "Register",
setup() {
const form = ref({
email: "",
password: "",
error: "",
});
const pressed = () => {
const user = firebase
.auth()
.createUserWithEmailAndPassword(form.value.email, form.value.password);
user
.then((user) => {
console.log(user);
const router = useRouter();
router.push("/about");
})
.catch((e) => console.log(e.message));
};
return {
form,
pressed,
};
},
});
</script>
Hope it is just something simpel

const router = useRouter(); must be declared in the scope of the setup hook not inside any inner scope :
setup(){
const router = useRouter();
const form = ref({
email: "",
password: "",
error: "",
});
const pressed = () => {
const user = firebase
.auth()
.createUserWithEmailAndPassword(form.value.email, form.value.password);
user
.then((user) => {
console.log(user);
router.push("/about");
})
.catch((e) => console.log(e.message));
};
return {
form,
pressed,
};
},

Related

Pinia action not updating state

When I call the login action in the login component I get the err Cannot set properties of undefined (setting 'user'). Why is user undefined when its defined in the state? Below is my Pinia store code
import {defineStore} from "pinia";
import axios from "axios";
import router from "#/router";
import {createToaster} from "#meforma/vue-toaster";
const toaster = createToaster({ position: "top-right"});
export const useStore = defineStore("main", {
state: () => ({
user: {},
token: null
}),
actions: {
login (email, password) {
axios.post('http://127.0.0.1:8000/api/login', {
email: email,
password: password
})
.then(function (response) {
const user = response.data.data.user
const token = response.data.data.access_token
// update pinia state
this.user = user
this.token = token
// store user details and jwt in local storage to keep user logged in between page refreshes
localStorage.setItem('user', JSON.stringify(user))
localStorage.setItem('token', token)
toaster.success(response.data.message)
// redirect to previous url or default to home page
router.push('/dashboard/home')
})
.catch(function (error) {
console.log(error)
//toaster.error(error.response.data.message)
})
}
}
})

Catch(error) on dispatched method in store not working in Vue 3

I am working on login of a vue 3 app, both the login and registration work fine, but i still need to throw send back a meaningful response to user if login in credentials are rejected by the back-end, i have tried every possible means to log the rejection response from server to console but to no avail, the login is fine when credential is correct, but the console just stay mute when incorrect credential is entered
this is my login.vue
import store from "../store"
import { useRouter } from "vue-router";
import { ref } from "vue";
const router = useRouter()
const user = { email: '', password: '', remember : false }
let errorMsg = ref('');
async function login(ev) {
ev.preventDefault();
await store.dispatch('login', user)
.then(()=> {
router.push({
name: 'Dashboard'
})
})
.catch((err) => {
errorMsg = err.response.data.error
console.log(err)
})
}
and this is my vuex store
import {createStore} from 'vuex'
import axiosClient from "../axios";
const store = createStore({
state: {
user: {
data: {},
token: sessionStorage.getItem('TOKEN')
}
},
getters: {},
setters: {},
actions: {
register({commit}, user) {
return axiosClient.post('/register', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
login({commit}, user) {
return axiosClient.post('/login', user)
.then(({data}) => {
commit('setUser', data);
return data;
})
},
},
mutations: {
logout: state => {
state.user.data = {};
state.user.token = null;
},
setUser: (state, userData)=> {
state.user.token = userData.token;
state.user.data = userData.user;
sessionStorage.setItem('TOKEN', userData.token)
}
},
modules: {}
})
export default store;
And here is my axios js file
import axios from "axios";
import store from "./store";
const axiosClient = axios.create({
baseURL: 'http://localhost:8000/api'
})
axiosClient.interceptors.request.use(config=> {
config.headers.Authorization = `Bearer ${store.state.user.token}`
return config;
})
export default axiosClient;
Response from backend as seen from Network Tab
{"error":"The provided credentials are incorrect","0":422}
After checking through my controller in my Laravel project, I discovered that I did not set the status code for the response properly.
Incorrect code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
422
]);
}
Corrected code:
if (!Auth::attempt($credentials, $remember)) {
return response([
'error'=> 'The provided credentials are incorrect',
], 422);
}
Axios does not treat the response received as a rejection; which needs to get its catch triggered.
Therefore my console.log that I had in my try/catch does not run at all.
I'm very happy we got this solved, big thanks to every one.

TypeError: Cannot read property 'token' of undefined store dispatch vuex

I have a vue component that makes use of the store Vuex. However I get a
TypeError: Cannot read property 'token' of undefined
error. I don't understand why. This is my code:
In main.js:
import Vue from 'vue'
import Vuex from 'vuex';
import App from './App.vue'
import router from './router';
import "./assets/css/tailwind.css";
import '#/assets/css/tailwind.css';
import store from './store';
Vue.config.productionTip = false;
Vue.use(Vuex);
new Vue({
router, store,
render: h => h(App),
}).$mount('#app');
In store/indes.js:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
},
state: {
token: ''
}
})
In GenericForm.vue:
methods: {
execute() {
console.log("GenericForm.vue")
if (this.inputFunction) {
this.inputFunction()
}
this.register()
},
register () {
console.log("register()")
try {
const response = AuthenticationService.register({
email: 'testss',
password: 'frgr'
})
this.$store.dispatch('setToken', response.data.token)
this.$store.dispatch('setUser', response.data.user)
this.$store.router.push({
name: 'songs'
})
} catch (error) {
console.log(error)
/*
this.error = error.response.data.error
*/
}
}
}
the error occurs on this line of code:
this.$store.dispatch
Any help is appreciated.
EDIT:
AuthenticationService.js
import api from './api'
export default {
register (credentials) {
return api().post('register', credentials)
}
}
api.js
import axios from 'axios'
export default() => {
return axios.create({
baseURL: 'http://localhost:8081'
})
};
After adding console.log:
EDIT2:
New method:
register: async () => {
console.log("register()")
const response = AuthenticationService.register({
email: 'testss',
password: 'frgr'
}).then((response) => {
console.log(response)
/* this.$store.dispatch('setToken', response.data.token)
this.$store.dispatch('setUser', response.data.user)*/
this.$store.router.push({
name: '/test'
})
});
}
I get the error on
this.$store.router.push({
name: '/test'
})
line:
The response gets logged alright, though.
There are two problems:
First problem:
This code:
register(credentials) {
return api().post('register', credentials)
}
is returning a Promise, which has no data property. What you want is to access the axios response wrapped in that promise, so you either:
call then on the promise
AuthenticationService.register({...}).then((response) => {
console.log(response.data.token) // 'foo'
});
use async/await inside the Vue component
Second problem
The problem that causes the store to be undefined, is the use of the arrow functions. The register() method shouldn't have an arrow. Once the arrow gets removed there is no error (store is defined, as well as a router):
async register() {
console.log("register()")
const response = AuthenticationService.register({
email: 'testss',
password: 'frgr'
}).then((response) => {
console.log(response)
console.log(this.$store)
this.$router.push({
name: 'ha'
})
});
}
This means that the data property of response is not defined.
Is the AuthenticationService.register method asynchronous?
I'd imagine it is. If so, your code is continuing before the response object has been properly resolved.
Take a second and run console.log(response). You may see an unresolved promise if the method is async.
Otherwise, you may see nothing defined at all if the method does not return anything but instead uses callbacks.

Vue Router fails to navigate from inside unit tests using jest

I have a router, Home, Login components and unit tests for the Login component.
The logic is: when user is unauthenticated, send him to Login page, once he's authenticated, send him to home page.
The logic works fine in the browser, however, when I run unit tests, I get an exception: thrown: undefined once the login component tries to navigate using this.$router.push('/');
In the console I see the message:
trying to route /login /
and then the exception is thrown once i run next();
Am I missing some setup to have the router working properly in the test environment?
Alternatively: is there a way to mock the next() function passed to the navigation guard?
Here's the router:
import VueRouter from 'vue-router';
import Home from '#/views/Home.vue';
import Login from '#/views/Login.vue';
import { state } from '#/store';
export const routes = [
{
path: '/',
name: 'home',
component: Home,
},
{
path: '/login',
name: 'login',
component: Login,
meta: {
noAuthRequired: true,
},
},
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
});
router.beforeEach((to: any, from: any, next: any) => {
console.log('trying to route', from.fullPath, to.fullPath);
const isAuthed = !!state.user.token;
if (!to.meta.noAuth && !isAuthed) {
next({ name: 'login' });
} else {
next();
}
});
export default router;
The component (relevant part):
import Vue from 'vue';
import Component from 'vue-class-component';
import { axios } from '../plugins/axios';
#Component
export default class App extends Vue {
private credentials = {
email: '',
password: '',
};
private error = '';
private async login() {
try {
const data = await axios.post('http://localhost:5000/api/v1/user/auth', this.credentials);
const token = data.data.payload;
this.$store.dispatch('setUser', { token });
this.error = '';
this.$router.push('/');
} catch (error) {
console.warn(error);
this.error = error;
}
}
}
And the unit test:
import Vue from 'vue';
import Vuetify from 'vuetify';
import AxiosMockAdapter from 'axios-mock-adapter';
import { Wrapper, shallowMount, createLocalVue } from '#vue/test-utils';
import flushPromises from 'flush-promises';
import Vuex, { Store } from 'vuex';
import { axios } from '#/plugins/axios';
import VTest from '#/plugins/directive-test';
import LoginPage from '#/views/Login.vue';
import { mainStore, state, IState } from '#/store';
import VueRouter from 'vue-router';
import router from '#/router';
describe('Login page tests', () => {
let page: Wrapper<Vue>;
let localStore: Store<IState>;
const localVue = createLocalVue();
const maxios = new AxiosMockAdapter(axios);
const vuetify = new Vuetify();
const errorMessage = 'Input payload validation failed';
const emailError = 'Invalid Email format';
const validData = {
email: 'valid#email.com',
password: 'test pass',
};
// in order for "click" action to submit the form,
// the v-btn component must be stubbed out with an HTML button
const VBtn = {
template: '<button type="submit"/>',
};
localVue.use(Vuetify);
localVue.directive('test', VTest);
localVue.use(Vuex);
localVue.use(VueRouter);
beforeAll(() => {
maxios.onPost().reply((body: any) => {
const jsonData = JSON.parse(body.data);
if (jsonData.email !== validData.email) {
return [400, {
message: errorMessage,
errors: { email: emailError },
}];
}
return [200, { payload: 'valid-token' }];
});
});
beforeEach(() => {
try {
localStore = new Vuex.Store({
...mainStore,
state: JSON.parse(JSON.stringify(state)),
});
page = shallowMount(LoginPage, {
store: localStore,
router,
localVue,
vuetify,
stubs: {
VBtn,
},
attachToDocument: true,
sync: false,
});
} catch (error) {
console.warn(error);
}
});
afterEach(() => {
maxios.resetHistory();
page.destroy();
});
const submitLoginForm = async (data: any) => {
page.find('[test-id="LoginEmailField"]').vm.$emit('input', data.email);
page.find('[test-id="LoginPasswordField"]').vm.$emit('input', data.password);
page.find('[test-id="LoginBtn"]').trigger('click');
await flushPromises();
};
it('Redirects user to home page after successful auth', async () => {
await submitLoginForm(validData);
expect(page.vm.$route.path).toEqual('/');
});
});

How To Watch Mutation In Vuex Plugin

I am having an issue with a plugin I am attempting to use. Note that code works fine after refreshing page however on initial login the mutation it is set to watch (createSession) is not responding correctly. I am not sure if anyone is familiar with the CASL package, but I don't think the issue is there but perhaps something I need to be doing to make the plugin work correctly.
Here is the plugin ability.js
import { Ability } from '#casl/ability'
export const ability = new Ability()
export const abilityPlugin = (store) => {
ability.update(store.state.rules)
const rules = store.subscribe((mutation) => {
switch (mutation.type) {
case 'createSession':
ability.update(mutation.payload.rules)
break
case 'destroySession':
ability.update([{ actions: '', subject: '' }])
break
}
console.log(ability.rules)
})
return rules
}
Here is the store where I am importing
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import { abilityPlugin, ability as appAbility } from './utils/ability'
import storage from './utils/storage'
export const ability = appAbility
Vue.use(Vuex)
axios.defaults.baseURL = 'http://traxit.test/api'
export default new Vuex.Store({
plugins: [
storage({
storedKeys: ['rules', 'token'],
destroyOn: ['destroySession']
}),
abilityPlugin
],
state: {
rules: '',
token: localStorage.getItem('access_token') || null,
sidebarOpen: true,
loading: false,
},
mutations: {
createSession(state, session) {
state.rules = session[0]
state.token = session.access_token
},
}
and I am mutation the createSession with my response data from the initial login action which is to retrieve token and rules here
retrieveToken({ commit }, credentials) {
return new Promise((resolve, reject) => {
axios.post('/login', {
username: credentials.username,
password: credentials.password,
})
.then(response => {
const token = response.data.access_token
localStorage.setItem('access_token', token)
commit('createSession', response.data)
resolve(response)
})
.catch(error => {
console.log(error)
reject(error)
})
})
},
any help would be greatly appreciated!! I have been stuck on this issue for a while..
Once again answering my own question. Lol
So after console loggin my mutation.payload I realized I was trying to access the data incorrecly.
I switched
case 'createSession':
ability.update(mutation.payload.rules)
break
to this
case 'createSession':
ability.update(mutation.payload[0])
break