I am curious if we can reach the current page's middleware from any component?
Middleware is executed prior to the component and is not initialized within the component in any way.
Update: Persisting the state of middleware in the auth store.
export const state = () => ({
isPrivate: false,
})
export const mutations = {
setPrivatePage(state, value) {
state.isPrivate = value
},
}
export const actions = {
privatePage({ commit }) {
commit('setPrivatePage', true)
},
publicPage({ commit }) {
commit('setPrivatePage', false)
},
}
Dispatch the action in the middleware/auth to set isPrivate:
await store.dispatch('auth/privatePage')
I added another middleware named public to set it back to false
export default async ({ store }) => {
await store.dispatch('auth/publicPage')
}
And finally to trigger public on every route, add it to the nuxt.config.js:
// Router
router: {
middleware: 'public',
},
You can access it on
$router.matched.meta.middleware
Related
I have just run into such a problem, I am trying to customize Axios module, My aim is to access my dom.js vuex module state from 'plugins' directory, The code below works but I have the following error in the console
Do not mutate vuex store state outside mutation handlers
So, The reason for this error is also clear to me, I wonder how I can Commit mutation from 'plugins' directory to my dom.js vuex module?
Thanks!
//plugins/axios.js
export default function ({ $axios, redirect, store}) {
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 401) {
store.state.dom.alertIs = true
redirect('/')
}
})
}
/store/dom.js
export const state = () => ({
alertIs:false
})
Declare a mutation (named "SET_DOM_ALERT") in your store:
// store/dom.js
export default {
state: () => ({
alertIs: false
}),
mutations: {
SET_DOM_ALERT(state, value) {
state.alertIs = value
}
}
}
Then, use store.commit('dom/SET_DOM_ALERT', newValue) in your plugin (notice the dom/ prefix for the namespace):
// plugins/axios.js
export default function ({ $axios, redirect, store}) {
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 401) {
store.commit('dom/SET_DOM_ALERT', true) // 👈
redirect('/')
}
})
}
demo
Scenario:
in user.js I have:
import * as mutationTypes from "../mutation-types";
import {user} from "./user_data";
export const state = {
user: user
...
}
export const getters = {
user: (state) => state.user,
...
};
export const mutations = {
[mutationTypes.SET_USER]: (state, payload) => {
state.user=payload;
},
...
);
export const actions = {
setUser: ({ commit }, payload) => {
commit(mutationTypes.SET_USER, payload);
},
...
);
export default {
state,
getters,
mutations,
actions,
};
now I want to move a method used in several pages from the .vue pages to this store page:
so I added to user.js actions the following:
getUser: async ({ commit }) =>{
this.user.loading=true;
try{
const res = await this.$http.post('/ajax/settings/settings_read.php');
if (res.data.errorid=='0')
{
let payload=res.data.user;
commit(mutationTypes.SET_USER, payload);
}
else
{
this.$router.push('/auth/login').catch(() => {});
}
} catch(e)
{
console.log(e);
}
this.user.loading=false;
},
and in .vue pages (actually I tried may different solutions adding async/await in several places)
import { mapActions } from "vuex";
...
created(){
this.$store.dispatch("getUser");
},
but does not work.
Can suggest the right way to move a method to vuex store?
Looks like this.user is reference to user in your state, hence
this.user.loading=true;
would change the state outside the mutation.
I am trying to load data from a JSON file into the VueX store, but the state does not get loaded until I try to refresh the VueX Store manually.
what I am trying to achieve is, before the app renders, the state should be loaded with the data.
Like before I access the homepage.
But I see on the Vue Devtools, that if set it to recording mode, then the app loads the data.
Below is code from store/index.js
//store/index.js
const exec = (method, { rootState, dispatch }, app) => {
const dispatches = [];
Object.keys(rootState).forEach(async (s) => {
dispatches.push(await dispatch(`${s}/${method}`, app));
});
return dispatches;
};
export const actions = {
nuxtServerInit(store, ctx) {
console.log('nuxtServerInit');
exec('init', store, ctx);
},
nuxtClientInit(store, ctx) {
console.log('nuxtClientInit');
exec('init', store, ctx);
},
init(store, ctx) {
console.log('nuxtInit');
exec('init', store, ctx);
},
};
store/app.js
//store/app.js
export const state = () => ({
config: {},
});
export const mutations = {
SET_CONFIG(state, config) {
state.config = config;
}
}
};
export const getters = {
config: (state) => state.config,
};
const loadConfig = ({ commit }) => {
const siteConfig = require('../config/data.json');
const appConfig = JSON.parse(JSON.stringify(siteConfig.properties));
commit('SET_CONFIG', appConfig);
};
export const actions = {
init(store, ctx) {
loadConfig(store);
},
};
Here the state is empty when the app loads. How can I access that when the app loads?
I normally call the init action of my store in the layout.
When this is too late you could also do it in a plugin, I guess.
You can use the context.store in the plugin.
// plugins/init.js
export default ({ store }) => {
store.dispatch("init")
}
// store/index.js
export actions = {
init(context) {
// ...
}
}
I need to get remote data to be displayed in every pages.
This call is perfomed in store/index.js:
export const state = () => ({
contact: {
hello: "World"
}
});
export const actions = {
async nuxtServerInit({ commit, state }) {
const { contactData } = await this.$axios.get("/contact");
commit("SET_CONTACT", contactData);
}
};
export const mutations = {
SET_CONTACT(state, contactData) {
state.contact = contactData;
}
};
Problem is that the value of contact turns to undefined in the store, whereas expected content is retrieved through Axios (the retrieved content is displayed in the SSR console...)
What am I missing here?
export const actions = {
async nuxtServerInit({ commit, state }, {app} ) {
const { contactData } = await app.$axios.get("/contact");
commit("SET_CONTACT", contactData);
}
};
I would like to auto-sign-in user when the page has been refreshed. I've read that I should use vuex-persistedstate to persist the token in localstorage. Here's my vuex store:
store: {
user: null
},
actions: {
autoSignIn ({commit}, payload) {
commit('setUser', { id: payload.token })
}
},
mutations: {
setUser (state, payload) {
state.user = payload;
}
},
plugins: [ createPersistedState({
getState: (key) => localStorage.getItem(key),
setState: (key, state) => localStorage.setItem('user_token', key)
}) ]
I also have signIn action where I create a newUser with token.
signUserIn ({commit, getters, state}, payload) {
let data = {
_username: payload.email,
_password: payload.password
}
Vue.http.post(
'url',
data,
{ channel: 'default' },
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
).then(response => {
const newUser = {
id: response.body.token
}
localStorage.setItem('user_token', response.body.token)
commit('setUser', newUser)
})
}
Then in main.js - created() I would like to check if the token is valid, afterwards - sign user in.
created() {
let token = localStorage.getItem('user_token')
if(token) {
this.$store.dispatch('autoSignIn', token)
}
}
The last part doesn't work, I know I should use getState, setState from createPersistedState but I have no idea how to do it. How do I make it work?
If the only use case for using vuex-persistedstate is to remember the access token then you should avoid using it in the first place and save yourself a few Kb from the final build file.
It would make more sense using it if you were to provide offline experience to your users.
If all you do is set state.user with the locally stored token then you could just do.
// if localStorage contains a serialized object with a 'token' attribute
const userToken = JSON.parse(window.localStorage.getItem('user_token'));
const state = {
user: userToken ? userToken.token || null : null
};
const mutations = {};
const actions = {};
export default {
state,
mutations,
actions,
}
Whenever you refresh the page and the store is being instantiated state.user will either take as default value the locally stored token or null if missing/undefined
However if i were you i would replace
const state = {
user: null
};
with
const state = {
accessToken: null
};
since all you store is the accessToken and not the user itself so its kind misleading.
update to answer the question in comments "... I need to check if the state has changed and use setUser mutation but don't how to achieve it."
There are 3 ways I can think of.
first of all change state to
const userToken = JSON.parse(window.localStorage.getItem('user_token'));
const state = {
accessToken: userToken ? userToken.token || null : null,
user: null,
};
then
The Simplest of all
on your App.vue component add a mounted method like the following
import { mapState, mapActions } from 'vuex';
export default {
...
computed: {
...mapState([
'accessToken',
'user',
])
},
mounted() {
if (this.accessToken && !this.user)
this.getAuthUser();
},
methods: {
...mapActions([
'getAuthUser',
]),
},
}
So on every refresh when the App is mounted and we have an accessToken but not a user we call getAuthUser() action which makes an ajax call and stores the received user with a setUser mutation
The Router Guard way
If you have a router and you only need to check for an authenticated user on certain routes then you can use route guards. for example
import store from '#/store';
export default new Router({
routes: [
...
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (!store.state.accessToken) return next('/login');
if (store.state.accessToken && !store.state.user) {
return store.dispatch('getAuthUser')
.then(() => {
// user was retrieved and stored and
// we can proceed
next();
})
.catch(() => {
// we couldn't fetch the user maybe because the token
// has expired.
// We clear the token
store.commit('accessToken', null);
// And go to login page
next('/login');
});
},
return next();
},
},
...
],
});
Using Vuex plugins
This is a method I've recently learned.
const storeModerator = (store, router) {
// listen to mutations
store.subscribe(({ type, payload }, state) => {
// if commit('setAccessToken') was called dispatch 'getAuthUser'
if (type === 'setAccessToken') {
store.dispatch('getAuthUser');
}
});
};
export default new Vuex.Store({
...,
plugins: [storeModerator]
});
You can learn more by checking:
Vue-router navigation guards
Vuex Plugins
Decouple Vuex modules with the Mediator pattern