Vuex 4 Modules can't use global axios property - vue.js

I have a Vue3 (without Typescript) app running Vuex 4.0.0.
I'm trying to set up a simple GET request in one of my store modules using axios, however I'm getting a Cannot read property of 'get' of undefined when I try to do it via an action called in one of my components, however if I call this.$axios from the component it works fine. For some reason, my modules can't use this.$axios, while elsewhere in the app I can.
I've declared $axios as a globalProperty in my main.js file.
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
import { store } from "./store";
import axios from "axios";
const app = createApp(App).use(store).use(router);
app.config.globalProperties.$axios = axios;
app.mount("#app");
And the store module looks like this (simplified for the purposes of this question):
// store/modules/example.js
const state = () => ({
message: ""
});
const mutations = {
getMessage(state, payload) {
state.message = payload;
}
};
const actions = {
async setMessage(commit) {
this.$axios.get("example.com/endpoint").then(response => {
commit("setMessage", response.message);
});
}
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
The main store/index.js that's getting imported in main.js above looks like this:
// store/index.js
import "es6-promise";
import { createStore } from "vuex";
import example from "./modules/example";
export const store = createStore({ modules: { example } });
In the component, I have the following:
// MyComponent.vue
import { mapGetters, mapActions } from "vuex";
export default {
computed: {
...mapGetters({
message: "example/getMessage"
})
},
methods: {
...mapActions({
getMessage: "example/setMessage"
})
}
};
And a simple button with #click="getMessage". Clicking this button, however, returns Uncaught (in promise) TypeError: Cannot read property 'get' of undefined, but if I copy/paste the setMessage action into a component method, it works just fine.
Is there a way for me to expose this.$axios to the store files?

While this is not the ideal solution, as I would've preferred to have my $axios instance available globally with a single declaration in the mount file, it's probably the next best thing.
I made a lib/axiosConfig.js file that exports an axios instance with some custom axios options, and I just import that one instance in every module that needs it.
import axios from "axios";
axios.defaults.baseURL= import.meta.env.DEV ? "http://localhost:8000": "example.com";
axios.defaults.headers.common["Authorization"] = "Bearer " + localStorage.getItem("token");
axios.defaults.headers.common["Content-Type"] = "application/json";
// etc...
export const $axios = axios.create();
And in whatever module I need $axios in, I just import { $axios } from "./lib/axiosConfig. It's not perfect as I mentioned, since I do still have to import it in every module, but it's close enough as far as I can see, and has the added benefit of using the same axios config everywhere by just importing this file.

Related

How does Vuex 4 createStore() work internally

I am having some difficulty understanding how Veux 4 createStore() works.
In /store/index.js I have (amongst a few other things):
export function createVuexStore(){
return createStore({
modules: {
userStore,
productStore
}
})
}
export function provideStore(store) {
provide('vuex-store', store)
}
In client-entry.js I pass the store to makeApp() like this:
import * as vuexStore from './store/index.js';
import makeApp from './main.js'
const _vuexStore = vuexStore.createVuexStore();
const {app, router} = makeApp({
vuexStore: _vuexStore,
});
And main.js default method does this:
export default function(args) {
const rootComponent = {
render: () => h(App),
components: { App },
setup() {
vuexStore.provideStore(args.vuexStore)
}
}
const app = (isSSR ? createSSRApp : createApp)(rootComponent);
app.use(args.vuexStore);
So, there is no store that is exported from anywhere which means that I cannot import store in another .js file like my vue-router and access the getters or dispatch actions.
import {store} '../store/index.js' // not possible
In order to make this work, I did the following in the vue-router.js file which works but I don't understand why it works:
import * as vuexStore from '../store/index.js'
const $store = vuexStore.createVuexStore();
async function testMe(to, from, next) {
$store.getters('getUser'); // returns info correctly
$store.dispatch('logout'); // this works fine
}
Does Veux's createStore() method create a fresh new store each time or is it a reference to the same store that was created in client-entry.js? It appears it is the latter, so does that mean an application only has one store no matter how many times you run createStore()? Why, then, does running createStore() not overwrite the existing store and initialise it with blank values?
createStore() method can be used on your setup method.
On your main.js, you could do something like this
import { createApp } from 'vue'
import store from './store'
createApp(App).use(store).use(router).mount('#app')
store.js
import { createStore } from 'vuex';
export default createStore({
state: {},
mutations: {},
actions: {},
});
To access your store, you don't need to import store.js anymore, you could just use the new useStore() method to create the object. You can directly access your store using it just as usual.
your-component.js
<script>
import { computed } from "vue";
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const isAuthenticated = computed(() => store.state.isAuthenticated);
const logout = () => store.dispatch("logout");
return { isAuthenticated, logout };
},
};
</script>
To use your store in the route.js file, you could simply imported the old fashion way.
route.js
import Home from '../views/Home.vue'
import store from '../store/'
const logout = () => {
store.dispatch("auth/logout");
}
export const routes = [
{
path: '/',
name: 'Home',
component: Home
}
]

How to get access to the Root inside the setup method of Vue 3 component?

I have an Vue App.
I use vuex.
I created my app like this:
import { createApp } from "vue";
import axios from "axios";
import App from "./App.vue";
import router from "./router";
import store from "./store/index";
axios.defaults.baseURL = "https://localhost:44349";
const app = createApp(App)
.use(router)
.use(store)
.mount("#app");
Than i one of my component i am trying to access context.root.$store in the setup() method
, but context.root is undefined.
<script>
import {ref, computed } from "vue";
export default {
name: "ClientList",
setup(props, context) {
const clients = ref([]);
console.log(context);
const clientOrdersLenght = computed(() => {
return clients.value.length;
});
return { clients, clientOrdersLenght }
},
};
</script>
My idea is get acces to my store via context.root. I watched videos and examples with this. but they refer to Vue 2 using 'vue/composition-api' as import.
What i am missing?
You might be able to include and access the store directly
store.dispatch('myModule/myModuleAction')
store.myModule.state.blabla
import {ref, computed } from "vue";
import store from './store/index'; // include store
export default {
name: "ClientList",
setup(props) {
const clients = ref([]);
const clientOrdersLength = computed(() => {
store.dispatch('myAction'); // call dispatch
store.state.myItem // or access store's state
return clients.value.length;
});
return { clients, clientOrdersLength }
},
};
I found an easy way to access my store with getStore() from vuex. That's cool.
But for other reason in the feature, me or somebody else will need to access the $root (vue instance) of the app. So may be now the correct question is how to get the $root (vue instance) of the app in a child component?

Use Vuex classic mode in Nuxt application

I'm rewriting Vue application to Nuxt architecture because we want SSR. However I don't want to rewrite Vuex store file which is:
import Vue from "vue";
import Vuex from "vuex";
import vuexI18n from "vuex-i18n/dist/vuex-i18n.umd.js";
import toEnglish from "../translations/toEnglish";
import toSpanish from "./../translations/toSpanish";
import toGerman from "./../translations/toGerman";
import toRussian from "./../translations/toRussian";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
currentLanguage: ''
},
mutations: {
changeLang: (state, response) => {
if(response) {
state.currentLanguage = response;
Vue.i18n.set(response);
console.log(response);
}
}
}
});
Vue.use(vuexI18n.plugin, store);
Vue.i18n.add("en", toEnglish);
Vue.i18n.add("es", toSpanish);
Vue.i18n.add("de", toGerman);
Vue.i18n.add("ru", toRussian);
export default store;
I know that Nuxt has some other approach to that but I really want to stick with above code. Unfortuenally I can't call mutation from my component by:
this.$store.commit('changeLang', lang)
it print error in console:
[vuex] unknown mutation type: changeLang
I also tried like this
this.$store.commit('store/changeLang', lang)
but error is same. How to fix it? Do I need to rewrite this vuex file in order to make it work?
I followed #Aldarund tips and changed above code to:
import Vue from "vue";
import Vuex from "vuex";
import vuexI18n from "vuex-i18n/dist/vuex-i18n.umd.js";
import toEnglish from "../translations/toEnglish";
import toSpanish from "./../translations/toSpanish";
import toGerman from "./../translations/toGerman";
import toRussian from "./../translations/toRussian";
const store = () => {
return new Vuex.Store({
state: () => ({
currentLanguage: ''
}),
mutations: {
changeLang: (state, response) => {
if (response) {
state.currentLanguage = response;
Vue.i18n.set(response);
console.log(response);
}
}
}
})
};
Vue.use(vuexI18n.plugin, store);
Vue.i18n.add("en", toEnglish);
Vue.i18n.add("es", toSpanish);
Vue.i18n.add("de", toGerman);
Vue.i18n.add("ru", toRussian);
export default store;
now error is
Uncaught TypeError: store.registerModule is not a function
It's probably because of Vue.use(vuexI18n.plugin, store);.
You need to use classic mode.
Classic (deprecated): store/index.js returns a method to create a
store instance
So it should look like this, no vuex use, on import of Vue. And it must be a function that crestr store, not plain vuex object
import Vuex from 'vuex'
const createStore = () => {
return new Vuex.Store({
state: () => ({
counter: 0
}),
mutations: {
increment (state) {
state.counter++
}
}
})
}
export default createStore
Docs https://nuxtjs.org/guide/vuex-store/#classic-mode
As for plugin e.g. vyexi18 you need to move that code into plugin file and get created store object from context https://nuxtjs.org/guide/plugins/
export default ({ store }) => {
Your vuex18initcode
}

vuex unknown action type when attempting to dispatch action from vuejs component

I'm using laravel, vue and vuex in another project with almost identical code and it's working great. I'm trying to adapt what I've done there to this project, using that code as boilerplate but I keep getting the error:
[vuex] unknown action type: panels/GET_PANEL
I have an index.js in the store directory which then imports namespaced store modules, to keep things tidy:
import Vue from "vue";
import Vuex from "vuex";
var axios = require("axios");
import users from "./users";
import subscriptions from "./subscriptions";
import blocks from "./blocks";
import panels from "./panels";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
},
actions: {
},
mutations: {
},
modules: {
users,
subscriptions,
blocks,
panels
}
})
panels.js:
const state = {
panel: []
}
const getters = {
}
const actions = {
GET_PANEL : async ({ state, commit }, panel_id) => {
let { data } = await axios.get('/api/panel/'+panel_id)
commit('SET_PANEL', data)
}
}
const mutations = {
SET_PANEL (state, panel) {
state.panel = panel
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
Below is the script section from my vue component:
<script>
import { mapState, mapActions } from "vuex";
export default {
data () {
return {
}
},
mounted() {
this.$store.dispatch('panels/GET_PANEL', 6)
},
computed:
mapState({
panel: state => state.panels.panel
}),
methods: {
...mapActions([
"panels/GET_PANEL"
])
}
}
</script>
And here is the relevant code from my app.js:
import Vue from 'vue';
import Vuex from 'vuex'
import store from './store';
Vue.use(Vuex)
const app = new Vue({
store: store,
}).$mount('#bsrwrap')
UPDATE:: I've tried to just log the initial state from vuex and I get: Error in mounted hook: "ReferenceError: panel is not defined. I tried creating another, very basic components using another module store, no luck there either. I checked my vuex version, 3.1.0, the latest. Seems to be something in the app.js or store, since the problem persists across multiple modules.
Once you have namespaced module use the following mapping:
...mapActions("panels", ["GET_PANEL"])
Where first argument is module's namespace and second is array of actions to map.

Import Axios Method Globally in Vuejs

this is my ~/plugins/axios.js file:
import axios from 'axios'
let api = axios.create({
baseURL: 'http://localhost:8000/api/v1/'
})
export default api
When I want to use axios in every components, I must have write this line:
import api from '~/plugins/axios
How can i config it globally, just write $api instead?
You can create a plugin and use it like this in your main.js file (if you're using something like vue-cli)
import axios from 'axios'
Vue.use({
install (Vue) {
Vue.prototype.$api = axios.create({
baseURL: 'http://localhost:8000/api/'
})
}
})
new Vue({
// your app here
})
Now, you can do this.$api.get(...) on every component method
Read more about Vue plugins here: https://v2.vuejs.org/v2/guide/plugins.html
Provide/Inject could be an option as well: https://v2.vuejs.org/v2/api/#provide-inject
There is a window object available to you in the browser. You can actively leverage that based on your requirements.
In the main.js file
import axiosApi from 'axios';
const axios = axiosApi.create({
baseURL:`your_base_url`,
headers:{ header:value }
});
//Use the window object to make it available globally.
window.axios = axios;
Now in your component.vue
methods:{
someMethod(){
axios.get('/endpoint').then(res => {}).catch(err => {});
}
}
This is basically how I use axios globally in my projects. Also, this is also how Laravel uses it.
Keeping this in main.js works perfectly fine for me in Vue 3.
import { createApp } from 'vue';
import App from './App.vue';
import axios from "axios";
const app = createApp(App);
const instance = axios.create({
baseURL: 'https://example.com/',
});
app.config.globalProperties.axios=instance
app.mount('#app');
and to use it in any component,
this.axios.post('/helloworld', {
name: this.name,
age: this.age
})