How To Watch Mutation In Vuex Plugin - vue.js

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

Related

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.

errors: `unknown mutation type: setLoggedIn` & `unknown local mutation type: setLoggedIn, global type: auth/setLoggedIn`

two errors help me build with vuex modules
errors: unknown mutation type: setLoggedIn & unknown local mutation type: setLoggedIn, global type: auth/setLoggedIn
vuex version "vuex": "^4.0.0"
the problem occurs in the setLoggedInState(ctx) function
index.js
import Vuex from 'vuex'
import middleware from "./modules/middleware.js";
import auth from "./modules/auth";
export default new Vuex.Store({
namespaced: true,
modules: {
auth,
middleware
}
})
auth.js
const state = {
isLoggedIn: true,
};
const mutation = {
setLoggedIn(state, payload, ) {
state.isLoggedIn = payload;
},
};
const actions = {
setLoggedInState(ctx) {
return new Promise((resolve) => {
if (localStorage.getItem('token')) {
ctx.commit('setLoggedIn', true, {root: true});
resolve(true)
} else {
ctx.commit('setLoggedIn', false, {root: true});
resolve(false)
}
});
},
}
const getters = {
loggedIn(state) {
return state.isLoggedIn;
},
export default {
namespaced: true,
state,
mutation,
actions,
getters
}
Dashboard
import {mapActions} from 'vuex'
export default {
name: "Dashboard",
data: () => ({}),
created() {
this.checkUserState();
},
methods: {
...mapActions({
checkUserState: 'auth/setLoggedInState',
}),
I don’t understand how to fix errors I tried many ways I hope for your help
When you learn something new, please check for missing '; , .' etc.
And be sure that you write 'const mutations' not 'const mutation', following the documentation saving hours))

vuex error with quasar $store.auth is undefined

I am trying to use vuex with Quasar. I have created an authentication module as below.
// src/store/auth/index.js
import { api } from 'boot/axios';
export default {
state: {
user: null,
},
getters: {
isAuthenticated: state => !!state.user,
StateUser: state => state.user,
},
mutations: {
setUser(state, username){
state.user = username
},
LogOut(state){
state.user = null
},
},
actions: {
LOGIN: ({ commit }, payload) => {
return new Promise((resolve, reject) => {
api
.post(`/api/login`, payload)
.then(({ data, status }) => {
if (status === 200) {
commit('setUser', data.refresh_token)
resolve(true);
}
})
.catch(error => {
reject(error);
});
});
},
}
}
I imported it in the store
// src/store/index.js
import { store } from 'quasar/wrappers'
import { createStore } from 'vuex'
import auth from './auth'
export default store(function (/* { ssrContext } */) {
const Store = createStore({
modules: {
auth:auth
},
// enable strict mode (adds overhead!)
// for dev mode and --debug builds only
strict: process.env.DEBUGGING
})
return Store
})
And I imported it into MainLayout to check if the user is logged in.
// src/layouts/MainLayout
<template>
</template>
<script>
import { ref, onMounted } from 'vue'
import packageInfo from '../../package.json'
import { useStore } from 'vuex'
export default {
name: 'MainLayout',
setup () {
const $store = useStore;
const connected = ref(false);
function checkLogin(){
//console.log($store)
return connected.value = $store.auth.isAuthenticated
};
onMounted(()=> {
checkLogin();
});
return {
appName: packageInfo.productName,
link:ref('dashboard'),
drawer: ref(false),
miniState: ref(true),
checkLogin,
}
}
}
</script>
But every time, I get the same error :
$store.auth is undefined
I tried to follow the quasar documentation, but I can't. Can anyone tell me what I am doing wrong please?
Thank you.
Someone helped me to find the solution. My error is to have written const $store = useStore instead of const $store = useStore(). Thanks

How can I test actions within a Vuex module?

I want to test a vuex module called user.
Initially, I successfully registered my module to Vuex. Its works as expected.
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
}
})
export default store
My user module is defined as follows
store/modules/user.js
const state = {
token: getToken() || '',
}
export const getters = {
token: state => state.token,
}
const mutations = {
[SET_TOKEN]: (state, token) => {
state.token = token
}
}
const actions = {
[LOGIN] ({ commit }, body) {
return new Promise((resolve, reject) => {
login(body).then(response => { //login is an api method, I'm using axios to call it.
const { token } = response.data
setToken(token)
commit(SET_TOKEN, token)
resolve()
}).catch(error => {
reject(error)
})
})
}
}
export default {
state,
getters,
mutations,
actions
}
login api
api/auth.js
import request from '#/utils/request'
export function login (data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
axios request file
utils/request
import axios from 'axios'
import store from '#/store'
import { getToken } from '#/utils/auth'
const request = axios.create({
baseURL: process.env.VUE_APP_BASE_API_URL,
timeout: 5000
})
request.interceptors.request.use(
config => {
const token = getToken()
if (token) {
config.headers['Authentication'] = token
}
return config
}
)
export default request
When I want to write some test (using Jest), for example login action as shown above.
// user.spec.js
import { createLocalVue } from '#vue/test-utils'
import Vuex from 'vuex'
import actions from '#/store/modules/user'
const localVue = createLocalVue()
localVue.use(Vuex)
test('huhu', () => {
expect(true).toBe(true)
// implementation..
})
How can I write test for my Login action? Thanks. Sorry for my beginner question.
EDIT: SOLVED Thank you Raynhour for showing to me right direction :)
import { LOGIN } from '#/store/action.types'
import { SET_TOKEN } from '#/store/mutation.types'
import { actions } from '#/store/modules/user'
import flushPromises from 'flush-promises'
jest.mock('#/router')
jest.mock('#/api/auth.js', () => {
return {
login: jest.fn().mockResolvedValue({ data: { token: 'token' } })
}
})
describe('actions', () => {
test('login olduktan sonra tokeni başarıyla attı mı?', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions[LOGIN](context, body)
await flushPromises()
expect(context.commit).toHaveBeenCalledWith(SET_TOKEN, 'token')
})
})
Store it's just a javascript file that will export an object. Not need to use vue test util.
import actions from '../actions'
import flushPromises from 'flush-promises'
jest.mock('../api/auth.js', () => {
return {
login: jest.fn()..mockResolvedValue('token')
}; // mocking API.
describe('actions', () => {
test('login should set token', async () => {
const context = {
commit: jest.fn()
}
const body = {
login: 'login',
password: 'password'
}
actions.login(context, body)
await flushPromises() // Flush all pending resolved promise handlers
expect(context.commit).toHaveBeenCalledWith('set_token', 'token')
})
})
but you need to remember that in unit tests all asynchronous requests must be mocked(with jest.mock or something else)

Problem accessing the vuex mutations from inside of the store/index.js

Humbly, I admit that I'm gleaning from SAVuegram in setting up authentication for my app. I am running Firebase's onAuthStateChanged to make certain the user stays logged in during page refreshes however I am completely unable to access the state inside of the function, receiving the following error.
(the $store is just from me taking a screenshot while testing "$store" as well as "store" - both break.)
With strict debugging turned on I'm also getting messages warning me about mutating outside of mutation handlers, but isn't that what I'm doing?
Here's my store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import fb from '../components/firebase/firebaseInit'
import createLogger from 'vuex/dist/logger'
// import dataRefs from './dataRefs'
// import pageTitle from './modules/pageTitle'
import auth from './modules/auth'
import apps from './modules/applicants'
Vue.use(Vuex)
fb.auth.onAuthStateChanged(user => {
if (user) {
// here I can access the user object but never the following "store"
console.log("FB AUTH", user);
store.commit('setCurrentUser', user)
store.dispatch('fetchUserProfile')
}
});
const debug = process.env.NODE_ENV !== 'production'
const createStore = () => {
return new Vuex.Store({
state: {
pageTitle: '',
currentUser: null,
userProfile: null
},
mutations: {
changePageTitle(state, newTitle) {
state.pageTitle = newTitle
},
changeSnackBarMessage(state, newMessage) {
state.snackBarMessage = newMessage
},
// POPUPS
changeAdvisorAddAppPopupStatus(state) {
state.showAdvisorAddPopup = !state.showAdvisorAddPopup
},
setToken(state, token) {
state.token = token;
},
setUser(state, userObj) {
state.user.email = userObj.email;
state.user.fbUUID = userObj.uuid;
}
},
actions: {
updatePageTitle(vuexContext, newTitle) {
vuexContext.commit('changePageTitle', newTitle);
},
updateSnackBarMessage(vuexContext, newMessage) {
vuexContext.commit('changeSnackBarMessage', newMessage);
},
toggleAdvisorAddPopup(vuexContext) {
vuexContext.changeAdvisorAddAppPopupStatus
},
},
modules: {
auth,
apps
},
strict: debug,
plugins: debug ? [createLogger()] : []
})
};
export default createStore;
FYI, I have also tried to instantiate my store by using export const store = new Vuex.Store({ instead of the const create ... method but then Vuex show me that none of my state, actions, getters, or mutations are even found.
Thanks for the help.
Why do you create the store in a function?
const createStore = () => {
I am just doing it like that:
const store = new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
auth,
apps
},
strict: debug,
plugins: debug ? [createLogger()] : []
})
};
export default store;
And i am able to access store now.