Vue 3 - How to dispatch to Vuex store in setup - vue.js

I have a project where I use Vue 3 and Vuex. This is my first time using Vue 3. I can't seem to get how to access Vuex inside the Setup method of a Vue 3 project.
I have a feature object. That is being set by a Childcomponent using the featureSelected method. Firstly in my setup I create a store constant with useStore; from import { useStore } from "vuex";. Then inside the featureSelected function I call the dispatch function on this store object store.dispatch("setPlot", { geometry: newFeature });.
I keep on getting an error telling me that the dispatch function does not exist on the store object: Uncaught TypeError: store.dispatch is not a function.
setup() {
const store = useStore;
const feature = ref();
const featureSelected = (newFeature) => {
feature.value = newFeature;
store.dispatch("setPlot", { geometry: newFeature });
};
return { feature, featureSelected };
},

useStore is a composable function which should be called using () like :
const store = useStore();

Related

Issue using Socket.io with Pinia

I'm trying to use Pinia to manage some global state in a Vue.js app I'm building, specifically I want to share a Socket.io instance between various components and views. However I'm getting a
this.socketObject.emit is not a function
error when calling functions from the Socket.io instance, when I call them from a component other than the component/view the Socket.io instance was created in. Here's some excerpts of the code.
#/views/LobbyView.vue (This is where I create the Socket.io instance and pass it to the Pinia store, I can use emit fine in this file without any errors)
import io from "socket.io-client";
import { useSocket} from "#/store/index";
...
setup() {
const socketObject = useSocket();
return { socketObject};
},
...
async mounted() {
this.socketObject = await io("http://localhost:8000");
this.socketObject.emit("createNewRoom");
}
#/store/index.js Pinia store
import { defineStore } from "pinia";
...
export const useSocket = defineStore({
id: "socket",
state: () => {
return {socketObject: Object};
},
getters: {},
actions: {},
});
#/components/lobbySettings (this is the file where I have issues using Socket.io in via my Pinia store)
import { useSocket } from "#/store/index";
...
setup() {
const socketObject = useSocket();
return { socketObject};
},
...
methods: {
startGame() {
this.socketObject.emit("updateRoom", this.roomInfo);
},
},
When the start game method is called on a button press, if I catch the error I get
this.socketObject.emit is not a function
I don't quite understand why Pinia isn't giving me access to functions from my Socket.io instance, the store seems to be working fine for other data in my app, just cant call these functions.
useSocket returns a store, not socket instance. It should be used as:
const socketStore = useSocket();
...
socketStore.socketObject.emit(...)
io(...) doesn't return a promise, it's semantically incorrect to use it with await.
The use of Object constructor is incorrect. If a value is uninitialized, it can be null:
state: () => {
return {socketObject: null};
},
The mutation of store state outside the store is a bad practice. All state modifications should be performed by actions, this way they can be easily tracked through devtools, this is one of benefits of using a store.
At this point there's no benefit from packing socketObject inside a store. Socket instance could be either used separately from a store, or socket instance could be abstracted away and made reactive with store actions, etc.

How to pass an argument to Pinia store?

I'm making a session API call in main.js and using values from the response as the initial value for my root store. In vuex it's handled this like,
DataService.getSession()
.then((sessionData) => {
new Vue({
i18n,
router,
// this params sessionData.session will be passed to my root store
store: store(sessionData.session),
render: (h) => h(App),
}).$mount('#app');
})
Consumed like,
export default function store(sessionData) { // here I'm getting the sessionData
return new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
state: {
// some states here
},
});
}
In case of Pinia we're creating a app instance & making it use like,
app.use(createPinia())
And my store would be like,
// how to get that sessionData here
import { defineStore } from 'pinia'
export const useCounterStore = defineStore({
id: 'counter',
state: () => ({
counter: 0
})
})
Is it possible to pass the sessionData someway to the pinia store?
There are 3 ways to pass parameters to a Pinia store - see the list below. You could use either #2 or #3 .
In most cases it is wise to initialise your Vue instance and show the user something while they are waiting for connections to resolve. So you may find it simpler to just access or initialise the store by calling DataService.getSession() in say a "SessionStore" action which can be async. Typically Components =access=> Stores =access=> Services.
Unlike Vuex, you don't need a root Pinia store. You can get just call useSomeStore() in the setup method for any component. Each store can just be an island of data. Pinia stores can reference other pinia store instances. This might be particularly useful if you're migrating a set of Vuex stores to Pinia and need to preserve the old Vuex tree of stores.
1. Pass common params to every action.
export const useStore = defineStore('store1', {
state: () => ({
...
}),
actions: {
action1(param1: string ... ) {
// use param1
}
}
});
2. Initialise store AFTER creating it
Only works if there's one instance of this store required
export const useStepStore = defineStore('store2', {
state: () => ({
param1: undefined | String,
param2: undefined | String,
...
}),
getters: {
getStuff() { return this.param1 + this.param2; }
}
actions: {
init(param1: string, param2: string) {
this.param1 = param1
this.param2 = param2
},
doStuff() {
// use this.param1
}
}
});
3. Use the factory pattern to dynamically create store instances
// export factory function
export function createSomeStore(storeId: string, param1: string ...) {
return defineStore(storeId, () => {
// Use param1 anywhere
})()
}
// Export store instances that can be shared between components ...
export const useAlphaStore = createSomeStore('alpha', 'value1');
export const useBetaStore = createSomeStore('beta', 'value2');
You could cache the session data in your store, and initialize the store's data with that:
In your store, export a function that receives the session data as an argument and returns createPinia() (a Vue plugin). Cache the session data in a module variable to be used later when defining the store.
Define a store that initializes its state to the session data cached above.
In main.js, pass the session data to the function created in step 1, and install the plugin with app.use().
// store.js
import { createPinia, defineStore } from 'pinia'
1️⃣
let initData = null
export const createStore = initStoreData => {
initData = { ...initStoreData }
return createPinia()
}
export const useUserStore = defineStore('users', {
state: () => initData, 2️⃣
})
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createStore } from './store'
import * as DataService from './data-service'
DataService.getSession().then(sessionData => {
createApp(App)
.use(createStore(sessionData)) 3️⃣
.mount('#app')
})
demo
When you create a store in Pinia using defineStore() you give it the initial state. So wherever you do that just pass the data into it and then do
defineStore('name', {
state: () => {
isAdmin: session.isAdmin,
someConstant: 17
},
actions: { ... }
});

How to get item by Id using Vue 3 Composition API using Axios

Im struggling using Axios and useRoute using Axios with Composition API. Here is the code on how to do it in using the Option API, how do I recreate it, the Vue-router docs not well documented at all right now.
async created() {
const result = await axios.get(`https://localhost:5001/api/artists/${this.$route.params.id}`
);
const artist = result.data;
this.artist = artist;
},
To convert that code to Composition API:
created() hook is effectively the same timing as setup(), so put that code in setup().
this.$route can be accessed via useRoute() (from vue-router#4).
artist can be declared as a data ref, returning it from setup() if used in the template.
To reactively fetch data from the API based on the id parameter, use watch on route.params.id. This watch call returns a function that stops the watcher, which is useful if you need to conditionally unwatch the reference.
import { useRoute } from 'vue-router'
import { ref, watch } from 'vue'
export default {
// 1
setup() {
const route = useRoute() // 2
const artist = ref(null) // 3
// 4
const unwatch = watch(() => route.params.id, (newId, oldId) => {
const result = await axios.get(`https://localhost:5001/api/artists/${newId}`)
artist.value = result.data
// run only once
unwatch()
})
return {
artist
}
}
}

How to use $set in Nuxt store mutations?

I'm trying to modify the value of a Vuex store object key, but I'm getting errors with the three following ways:
export const mutations = {
USER_UPDATE(state, payload) {
this.$set(state.user, 'name', payload.name), // test 1
// vm.$set(state.user, 'name', payload.name), // test 2
// Vue.$set(state.user, 'name', payload.name)}, // test 3
}
}
With this. and vm., I get ReferenceError: vm is not defined
With Vue. and import Vue from 'vue' at the top of my store/index.js file, I get TypeError: vue__WEBPACK_IMPORTED_MODULE_6__.default.$set is not a function
What's the correct way to use $set with Nuxt store mutations ?
I finally fixed it using this syntax:
export const mutations = {
USER_UPDATE(state, payload) {
this._vm.$set(state.user, 'name', payload.name);
}
}

Why is my getter from store in App.vue class undefined

I am building an electron app using vue, vuex and vuex-typescript. So I have the following code for my store using vuex-typescript:
export const dispaVuex = {
namespaced: true,
state: {
dispatch: new Dispatcher(appState),
},
getters: {
getFTime(state: DispatchState): boolean {
return state.dispatch.fistTime;
},
},
};
const {read} = getStoreAccessors<DispatchState, RootState>("Test");
export const readFtime = read(dispaVuex.getters.getFTime);
After adding the store to my vue instance I try and access the firstTime variable in my App.vue like this:
#Component
export default class App extends Vue {
// fTime: boolean;
get fTime(): boolean {
return readFtime(this.$store);
}
}
When looking at the debugger, everything in the store is initialized perfectly but my App instance has fTime as undefined.
Why would this be the case? Is there something I am not getting about the order of how things are made?
PS. firstTime is a member of the class Dispatcher
So after looking closely at the example given in the vuex-typescript readme, I noticed that I was passing the wrong namespace when calling the function getStoreAccessors.
Using dispaVuex instead of Test like this fixed the problem:
const {read} = getStoreAccessors<DispatchState, RootState>("dispaVuex");