I am using vuex, axios for my app and I want to use getter in axios initiation to pass basic auth. This is my axios init (http-common.js):
import axios from 'axios'
import store from '#/store'
export default axios.create({
baseURL: 'http://localhost:8081/',
auth: store.getters['authentification']
})
When I am debugging my app I find store undefined. Can someone explain what am I doing wrong? Store itself works fine in all the components.
My store has several modules and those modules. store index.js:
import m1 from './modules/m1'
import m2 from './modules/m2'
import authentification from './modules/authentification'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
authentification,
m1,
m2
}
})
And modules uses axios init function for calling REST api i.e. :
import HTTP from '#/common/http-common'
.....
const actions = {
action ({commit}) {
HTTP.get('item')
.then(response => {
commit('MUTATION', {response})
})
}
}
.....
export default {
state,
getters,
actions,
mutations
}
I think this creates loop and calls http-common before store is being initialized.
Edit: adding authentification module as requested:
import * as types from '../mutation-types'
const state = {
isLoggedIn: !!localStorage.getItem('auth'),
auth: JSON.parse(localStorage.getItem('auth'))
}
const getters = {
isLoggedIn: state => {
return state.isLoggedIn
},
authentification: state => {
return state.auth
}
}
const mutations = {
[types.LOGIN] (state) {
state.pending = true
},
[types.LOGIN_SUCCESS] (state) {
state.isLoggedIn = true
state.pending = false
},
[types.LOGOUT] (state) {
state.isLoggedIn = false
}
}
const actions = {
login ({
state,
commit,
rootState
}, creds) {
console.log('login...', creds)
commit(types.LOGIN) // show spinner
return new Promise(resolve => {
setTimeout(() => {
localStorage.setItem('auth', JSON.stringify(creds))
commit(types.LOGIN_SUCCESS)
resolve()
}, 1000)
})
},
logout ({ commit }) {
localStorage.removeItem('auth')
commit(types.LOGOUT)
}
}
export default {
state,
getters,
actions,
mutations
}
This is actually a better solution shown to me by Thorsten Lünborg (LinusBorg) of the Vue core team:
https://codesandbox.io/s/vn8llq9437
In the file that you define your Axios instance in and set configuration, etc., you have also got a Vuex plugin that watches your store and sets/deletes your Authorization header based on the presence of whatever auth token in your store.
I have found a sollution. I had to assign auth before the call and not during inicialization of axios object:
var axiosInstance = axios.create({
baseURL: 'http://localhost:8081/'
})
axiosInstance.interceptors.request.use(
config => {
config.auth = store.getters['authentification']
return config
}, error => Promise.reject(error))
export default axiosInstance
Related
I'm using redux-thunk for async actions, and all was working as expected until I added an Apollo Client into the mix, and I can't figure out why. The action is being dispatched, but the return function is not being called.
-- Client provider so that I can use the client in the Redux store outside of the components wrapped in <ApolloProvider>.
import { ApolloClient, InMemoryCache, createHttpLink } from "#apollo/client";
class ApolloClientProvider {
constructor() {
this.client = new ApolloClient({
link: createHttpLink({ uri: process.env.gqlEndpoint }),
cache: new InMemoryCache(),
});
}
}
export default new ApolloClientProvider();
-- My store setup
const client = ApolloClientProvider.client;
const persistConfig = {
key: "root",
storage: storage,
};
const pReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(
pReducer,
applyMiddleware(thunk.withExtraArgument(client))
);
-- The action
export const fetchMakeCache = (token) => (dispatch, client) => {
console.log("fetching");
const query = gql`
query Source {
sources {
UID
Name
ActiveRevDestination
}
workspaces {
UID
Name
SourceUids
ActiveRevSource
}
}
`;
return () => {
console.log("reached return");
dispatch(requestMakeCache());
client
.query({
query: query,
context: {
headers: {
Authorization: `Bearer ${token}`,
},
},
})
.then((r) => r.json())
.then((data) => dispatch(receivedMakeCache(data)))
.catch((error) => dispatch(failedMakeCache(error)));
};
};
-- The component dispatching the thunk
import React from "react";
import { useAuth0 } from "#auth0/auth0-react";
import { useDispatch } from "react-redux";
import * as actions from "../store/actions";
const Fetch = () => {
const dispatch = useDispatch();
const { getAccessTokenSilently, isAuthenticated } = useAuth0();
if (isAuthenticated) {
// This will set loading = false immediately
// The async function below results in loading not being set to false
// until other components are performing actions that will error
dispatch(actions.requestMakeCache());
(async () => {
const token = await getAccessTokenSilently({
audience: process.env.audience,
});
dispatch(actions.fetchMakeCache(await token));
})();
}
return <></>;
};
export default Fetch;
When the Fetch component loads, the "fetching" console log prints so it's definitely being dispatched. But the "reached return" never gets hit. This exact same code worked as expected when not using the Apollo client. However, I've been able to use the same client successfully in a component. I'm not getting any errors, the return function just isn't being hit.
Most of the questions on here about thunks not running the return function have to do with not dispatching correctly, but I don't think that's the case since this worked pre-Apollo. (Yes, I know that using redux and Apollo together isn't ideal, but this is what I have to do right now)
I am starting adventure with Vuex, and making some auth module. Found some examples that I am trying to follow, but got stuck trying to use axios in store. My store has separated index, actions, getters etc. files.
Action login works when it's in actions in store/index.js, but when I put it in the store/actions.js file, it says axios is undefined.
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
// import example from './module-example'
Vue.use(Vuex)
/*
* If not building with SSR mode, you can
* directly export the Store instantiation
*/
import state from './state'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
export default function(/* { ssrContext } */) {
const Store = new Vuex.Store({
modules: {
// example
},
namespaced: false,
getters,
mutations,
actions,
state,
// enable strict mode (adds overhead!)
// for dev mode only
strict: process.env.DEV,
})
return Store
}
store/actions.js
export default {
login({ commit }, user){
return new Promise((resolve, reject) => {
commit('auth_request')
axios({ url: 'http://localhost:3000/login', data: user, method: 'POST' })
.then(resp => {
const token = resp.data.token
const user = resp.data.user
localStorage.setItem('token', token)
axios.defaults.headers.common['Authorization'] = token
commit('auth_success', token, user)
resolve(resp)
})
.catch(err => {
commit('auth_error')
localStorage.removeItem('token')
reject(err)
})
})
}
}
import axios from 'axios'
That should be only in actions.js file, because that's where you use it. And yes, you need to import it in every file separately or use globals.
define axios globaly in app entry point(app.js or smth like that):
global.axios = require('axios')
I'm trying to split up my Nuxt Vuex store files into separate files. And NOT have all Vuex getters, mutations and actions into one huge file. This demo project is on Github by the way.
I'v read this official Nuxt Vuex Store documentation; but can't seem to get it working. It's a bit vague on where to put stuff.
I have the following in these files:
Below is my: store/index.js
import Vue from "vue";
import Vuex from "vuex";
import Auth from "./modules/auth";
Vue.use(Vuex);
export const store = () => {
return new Vuex.Store({
state: {
},
modules: {
Auth
}
})
}
This is in my: store/auth.js
const state = () => {
username: null
};
const getters = {
username: state => {
return state.username;
},
isAuthenticated: state => {
return state.username != null;
}
};
const mutations = {
login: (vuexContext, username) => {
vuexContext.username = username;
this.$router.push("/dashboard");
},
logout: vuexContext => {
vuexContext.username = null;
this.$router.push("/");
}
};
const actions = {
};
export default {
state,
getters,
mutations,
actions,
};
And finally in my: pages/index.vue
This is where I'm calling that login mutation:
<script>
export default {
layout: "non-logged-in",
data() {
return {
username: null
}
},
methods: {
onSubmit() {
this.$store.commit("login", this.username);
}
}
}
</script>
The error I'm getting:
[vuex] unknown mutation type: login
What am I doing wrong here? I thought i'm importing all the stuff correctly in the store/index.js
You have to export your store constant like this inside your store/index.js file:
export default store
Put this code line at the end of your file.
So as #jeremy.raza described this is what I changed in order to get it working:
store/index.js
import Vue from "vue";
import Vuex from "vuex";
import Auth from "./modules/auth";
Vue.use(Vuex)
const store = () => {
return new Vuex.Store({
state: {
},
modules: {
Auth
}
})
}
export default store;
Changes in the store/auth.js
Note the changes in how I wrote the state, getters and mutations method notation.
const state = () => ({
username: null
});
const getters = {
username(state) {
return state.username;
},
isAuthenticated(state) {
return state.username != null;
}
};
const mutations = {
login(vuexContext, username) {
vuexContext.username = username;
this.$router.push("/dashboard");
},
logout(vuexContext) {
vuexContext.username = null;
this.$router.push("/");
}
};
const actions = {
};
export default {
state,
getters,
mutations,
actions,
};
I'm a little bit confused with vuex store component.
How should I obtain state of another module?
I tried a different ways to get data from store and always got Observer object. What is the correct way to knock knock to observer?
If I try to get anything from this object directly, like rootState.user.someVariable then I got undefined response.
Don't have a problem getting state from components.
Edit. Add code
User module
import * as Constants from './../../constants/constants'
import * as types from '../mutation-types'
import axios from 'axios'
const state = { user: [] }
const getters = {
getUser: state => state.user
}
const actions = {
getUserAction ({commit}) {
axios({method: 'GET', 'url': Constants.API_SERVER + 'site/user'})
.then(result => {
let data = result.data
commit(types.GET_USER, {data})
}, error => {
commit(types.GET_USER, {})
console.log(error.toString())
})
}
}
const mutations = {
[types.GET_USER] (state, {data}) {
state.user = data
}
}
export default { state, getters, actions, mutations }
Mutatinos
export const GET_LANGS = 'GET_LANGS'
export const GET_USER = 'GET_USER'
Store
import Vuex from 'vuex'
import Vue from 'vue'
import user from './modules/user'
import lang from './modules/lang'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user,
lang
}
})
Main app
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/index'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
template: '<App/>',
components: { App }
})
Lang module, here is the place where I'm trying get store
import * as types from '../mutation-types'
import {axiosget} from '../../api/api'
const state = { langList: [] }
const getters = {
getLangs: state => state.langList
}
const actions = {
// this two action give me similar result
getLangsAction (context) {
axiosget('lang') // described below
},
getAnotherLangsAction (context) {
console.log(context.rootState.user) <----get Observer object
}
}
const mutations = {
[types.GET_LANGS] (state, {data}) {
state.langList = data
}
}
export default { state, getters, actions, mutations }
axiosget action, api module
import * as Constants from './../constants/constants'
import store from '../store/index'
import axios from 'axios'
export const axiosget = function (apiUrl, actionSuccess, actionError) {
console.debug(store.state.user) // <----get Observer object, previously described
// should append user token to axios url, located at store.state.user.access_token.token
axios({method: 'GET', 'url': Constants.API_URL + apiUrl
+ '?access_token=' + store.state.user.access_token.token})
.then(result => {
let data = result.data
// todo implement this
// }
}, error => {
if (actionError && actionError === 'function') {
// implement this
}
})
}
Component, that call dispatcher. If i get state via mapGetters in computed properties - there is no problems
<template>
<div>
{{user.access_token.token}}
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'ArticlesList',
computed: mapGetters({
user: 'getUser'
}),
created () {
this.$store.dispatch('getLangsAction')
this.$store.dispatch('getAnotherLangsAction')
}
}
</script>
What I'm trying to do in this code - get user access token in main site (after login) and all further manipulations with data will be produced via api host.
Let's say you want to fetch state an attribute userId from object userDetails in Vuex store module user.js.
userDetails:{
userId: 1,
username: "Anything"
}
You can access it in following way in action
authenticateUser(vuexContext, details) {
userId = vuexContext.rootState.user.userDetails.userId;
}
Note: After rootState and before file name user, add the path to the store module file if it is inside nested folders.
I am using VueJS in conjunction with vuex and vue-router. I have a vuex module that is making a mutation to its store, and trying to use that to determine whether or not a user is authenticated.
Here is what my code looks like in relevant part.
main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
router.beforeEach((to, from, next) => {
console.log(router.app) // prints a Vue$2 object
console.log(router.app.$store) // undefined
console.log(store.getters.isAuthenticated) // false
...
}
const app = new Vue({
store,
router,
...App
})
app.$mount('#app')
/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import core from './modules/core'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
core: core
}
})
export default store
/store/modules/core.js
import * as types from '../types'
import api from '../../api'
import router from '../../router'
const state = {
token: null,
user: null,
authenticated: false
}
const mutations = {
[types.LOGIN_SUCCESS] (state, payload) {
console.log('mutate')
state.token = payload.token
state.user = payload.user
state.authenticated = true
router.go('/')
}
}
const getters = {
isAuthenticated: state => {
return state.authenticated
}
}
const actions = {
[types.LOGIN] (context, payload) {
api.getToken(payload).then(response => {
context.commit(types.LOGIN_SUCCESS, response)
})
}
}
export default {
state,
mutations,
actions,
getters
}
When I go thru my logic to trigger the LOGIN action, I can see that the mutation executed properly, and when I use the Chrome extension to view the vuex state for my core module, the state for user and authenticated have been properly mutated.
QUESTION
It seems like this module just simply has not been loaded by the time the router is running in the .beforeEach loop. Is this true?
If yes, what are some other suggestions on how to handle this situation?
If no, what am I doing incorrect?
console.log(store.state.core.authenticated) return false because you not make a login yet.
In your code you not persist user info in anywhere. E.g. using localstorage
Same considerations:
Not use router.app.$store, use store that you import
In your LOGIN_SUCCESS mutation, store login info and token into localstorage
In your beforeEach hook, check localstorage, if was populated with token, get user information and apply the mutation. If not, just call login page
Something like this..
const mutations = {
[types.LOGIN_SUCCESS] (state, payload) {
state.token = payload.token
state.user = payload.user
state.authenticated = true
localstorage.setItem('token', payload.token)
localstorage.setItem('user', payload.user)
}
}
const actions = {
[types.LOGIN] (context, payload) {
return api.getToken(payload).then(response => {
context.commit(types.LOGIN_SUCCESS, response)
return response
})
}
}
router.beforeEach((to, from, next) => {
let user = localstorage.getItem('user')
let token = localstorage.getItem('token')
if (user && token) {
store.commit(types.LOGIN_SUCCESS, {token, user})
next()
}
else if (!store.getters.isAuthenticated) {
store.dispatch(types.LOGIN).then(() => next())
} else {
next()
}
}