How to pass an argument to Pinia store? - vuex

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: { ... }
});

Related

Cannot use Vue-Router to get the parameters in the URL

Today, when trying to use Vue-Router (in Vue-CLI) to get URL parameters, I encountered difficulties ($route.query is empty), the code is as follows.
Code purpose: Get the parameters carried after the URL (such as client_id in "http://localhost:8080/#/?client_id=00000000000077")
Project file structure:
router/index.js:
App.vue(Get part of the code for URL parameters):
The running result of this part of the code:
I'm not sure why $router.currentRoute and $route aren't matching up, but you could simply use $router.currentRoute.query.client_id if you need it in mounted().
Another workaround is to use a $watch on $route.query.client_id:
export default {
mounted() {
const unwatch = this.$watch('$route.query.client_id', clientId => {
console.log({ clientId })
// no need to continue watching
unwatch()
})
}
}
Or watch in the Composition API:
import { watch } from 'vue'
import { useRoute } from 'vue-router'
export default {
mounted() {
console.log({
route: this.$route,
router: this.$router,
})
},
setup() {
const route = useRoute()
const unwatch = watch(() => route.query.client_id, clientId => {
console.log({ clientId })
// no need to continue watching
unwatch()
})
}
}

Why vuex return object instead of array in Quasar App?

I have Quasar App
I connected vuex
I created a separate file as module for vuex like this:
I created an empty array in vuex
If I open Vue extension in browser I see that vuex return object where an array is stored instead of just an array
How can I store just an array instead of object?
I created a vuex file like this:
in store/index.js file I imported cars.js file:
import Vue from 'vue'
import Vuex from 'vuex'
import cars from "src/store/module-example/cars";
Vue.use(Vuex)
export default function (/* { ssrContext } */) {
const Store = new Vuex.Store({
modules: {
cars
},
// enable strict mode (adds overhead!)
// for dev mode only
strict: process.env.DEBUGGING
})
return Store
}
Cars.js file:
import axios from "axios";
const state = {
cars: []
}
const mutations = {
SET_CARS: (state, cars) => {
state.cars = cars;
}
}
const actions = {
async GET_ALL_CARS_FROM_API({commit}) {
let result = await axios.get('http://localhost:8050/api/cars');
commit("SET_CARS", result.data)
}
}
const getters = {
GET_ALL_CARS: (state) => {
return state.cars;
}
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
result.data is an object. In order to set cars to a plain array just set it to result.data.cars
commit("SET_CARS", result.data.cars)

Nuxt access store (in Module mode) from JS file

I have an AuthService that I use in a namespaced store in my Nuxt app. I need to commit mutations from AuthService to the namespaced store but I can't figure out how to import the store into my AuthService.
I've seen examples where the store is imported into the JS file, but the store is explicitly defined in the Vue app. Because I'm using Nuxt with the Module mode for my store, I'm not sure of the root path where I can import my store into the AuthService file. As I understand it, Nuxt handles creating the root store and all the namespaced store behind the scenes when use "Module mode"
My Nuxt store directory includes index.js (which is empty) and auth.js which has the mutations I want to call from AuthService.
auth.js
import AuthService from '../firebase/authService'
const authService = new AuthService()
export const state = () => ({
user: null
})
export const mutations = {
setUser (state, user) {
state.user = user
}
}
export const actions = {
async signUp ({ commit }, payload) {
try {
await authServices.createUser(payload)
return Promise.resolve()
} catch (err) {
const notification = {
duration: 5000,
message: err.message,
type: 'error'
}
commit('ui/activateNotification', notification, { root: true })
return Promise.reject()
}
}
}
authService.js
import { fAuth, fDb } from './config'
// I think I need to import auth store here but I'm not sure how
export default class AuthClient {
async createUser (payload) {
try {
const res = await fAuth.createUserWithEmailAndPassword(payload.email, payload.password)
const { uid } = res.user
const user = {
...payload,
uid
}
await this._createUserDoc(user)
this._initAuthListener()
return Promise.resolve()
} catch (err) {
return Promise.reject(err)
}
}
async _createUserDoc (user) {
await fDb.collection('users').doc(user.uid).set(user)
}
_initAuthListener () {
fAuth.onAuthStateChanged(async (user) => {
try {
if (user) {
const userProfileRef = fDb.collection('users').doc(user.uid)
const userProfileDoc = await userProfileRef.get()
const { uid, userName } = userProfileDoc.data()
// Here is where I want to call a mutation from the auth store
this.store.commit('setUser', {
uid,
userName
})
} else {
this.store.commit('setUser', null)
}
} catch (err) {
console.log(err)
}
})
}
}
I'd like to propose a solution using a plugin.
In the external module (externalModule.js) we define store variable and export an init function that receives Nuxt context as argument. The function assignes the store from context to the variable which can be now used in the module:
let store;
export function init (context) {
store = context.store;
};
(...further business logic using store)
Then in the plugins folder we create a plugin file (let's call it storeInit.js). The file imports the init function from the external module and exports default plugin function required by Nuxt. The function receives context from Nuxt and we call the init function passing the context further:
import { init } from '[pathTo]/externalModule.js';
export default (context, inject) => {
init(context);
};
Then we register the plugin in the nuxt.config.js file:
module.exports = {
...
plugins: [
{ src: '~/plugins/storeInit' }
],
...
}
This way when the app is built by Nuxt and plugins are registered, the context object is passed to the external module and we can use anything from it, among others the store.
In index.js file which is in store folder you need to return store like this
import Vuex from 'vuex'
const createStore = () => {
return new Vuex.Store({
state: {
counter: 0
},
mutations: {
increment (state) {
state.counter++
}
}
})
}
export default createStore
and in your authService.js file you need to import store like this
import $store from '~/store'
by this you will be able to access your store
$store.commit('setUser', null)
I hope this works for you
Important Note: you don't need to install vuex because it is already shipped with nuxtjs
You can access as window.$nuxt.$store
Note: My nuxt version is 2.14.11

Unable to access vuex-orm $db variable from store in nuxt

Using nuxtjs with vuexorm & vuexorm-axios plugin.
/pages/index.vue
computed: {
users() {
// this works as expected
return User.all()
}
}
plugins/vuex-orm-axios.js
import { Model } from '#vuex-orm/core'
export default ({ $axios }) => {
Model.setAxios($axios)
}
store/index.js
import VuexORM from '#vuex-orm/core'
import VuexORMAxios from '#vuex-orm/plugin-axios'
import User from '#/models/user'
VuexORM.use(VuexORMAxios)
// Create a new database instance.
const database = new VuexORM.Database()
// Register Models to the database.
database.register(User)
export const plugins = [
VuexORM.install(database)
]
Above all works. But in vuexorm docs it says to always fetch model from injected database instance for nuxt/ssr apps.
But if I try to access the $db variable from store it wont work as there is no $db variable inside store.
/pages/index.vue
computed: {
users() {
// this wont work as $db is undefined
User () {
return this.$store.$db().model('users')
},
users () {
return this.User.all()
}
}
What am I doing wrong here?
In your store/index.js you have to export state to activate Vuex store. Add this to the file:
export const state = () => ({})

How to use Vue Router from Vuex state?

In my components I've been using:
this.$router.push({ name: 'home', params: { id: this.searchText }});
To change route. I've now moved a method into my Vuex actions, and of course this.$router no longer works. Nor does Vue.router. So, how do I call router methods from the Vuex state, please?
I'm assuming vuex-router-sync won't help here as you need the router instance.
Therefore although this doesn't feel ideal you could set the instance as a global within webpack, i.e.
global.router = new VueRouter({
routes
})
const app = new Vue({
router
...
now you should be able to: router.push({ name: 'home', params: { id: 1234 }}) from anywhere within your app
As an alternative if you don't like the idea of the above you could return a Promise from your action. Then if the action completes successfully I assume it calls a mutation or something and you can resolve the Promise. However if it fails and whatever condition the redirect needs is hit you reject the Promise.
This way you can move the routers redirect into a component that simply catches the rejected Promise and fires the vue-router push, i.e.
# vuex
actions: {
foo: ({ commit }, payload) =>
new Promise((resolve, reject) => {
if (payload.title) {
commit('updateTitle', payload.title)
resolve()
} else {
reject()
}
})
# component
methods: {
updateFoo () {
this.$store.dispatch('foo', {})
.then(response => { // success })
.catch(response => {
// fail
this.$router.push({ name: 'home', params: { id: 1234 }})
})
I a situation, I find myself to use .go instead of .push.
Sorry, no explanation about why, but in my case it worked. I leave this for future Googlers like me.
I believe rootState.router will be available in your actions, assuming you passed router as an option in your main Vue constructor.
As GuyC mentioned, I was also thinking you may be better off returning a promise from your action and routing after it resolves. In simple terms: dispatch(YOUR_ACTION).then(router.push()).
state: {
anyObj: {}, // Just filler
_router: null // place holder for router ref
},
mutations: {
/***
* All stores that have this mutation will run it
*
* You can call this in App mount, eg...
* mounted () {
* let vm = this
* vm.$store.commit('setRouter', vm.$router)
* }
*
setRouter (state, routerRef) {
state._router = routerRef
}
},
actions: {
/***
* You can then use the router like this
* ---
someAction ({ state }) {
if (state._router) {
state._router.push('/somewhere_else')
} else {
console.log('You forgot to set the router silly')
}
}
}
}
Update
After I published this answer I noticed that defining it the way I presented Typescript stopped detecting fields of state. I assume that's because I used any as a type. I probably could manually define the type, but it sounds like repeating yourself to me. That's way for now I ended up with a function instead of extending a class (I would be glad for letting me know some other solution if someone knows it).
import { Store } from 'vuex'
import VueRouter from 'vue-router'
// ...
export default (router: VueRouter) => {
return new Store({
// router = Vue.observable(router) // You can either do that...
super({
state: {
// router // ... or add `router` to `store` if You need it to be reactive.
// ...
},
// ...
})
}
import Vue from 'vue'
import App from './App.vue'
import createStore from './store'
// ...
new Vue({
router,
store: createStore(router),
render: createElement => createElement(App)
}).$mount('#app')
Initial answer content
I personally just made a wrapper for a typical Store class.
import { Store } from 'vuex'
import VueRouter from 'vue-router'
// ...
export default class extends Store<any> {
constructor (router: VueRouter) {
// router = Vue.observable(router) // You can either do that...
super({
state: {
// router // ... or add `router` to `store` if You need it to be reactive.
// ...
},
// ...
})
}
}
If You need $route You can just use router.currentRoute. Just remember You rather need router reactive if You want Your getters with router.currentRoute to work as expected.
And in "main.ts" (or ".js") I just use it with new Store(router).
import Vue from 'vue'
import App from './App.vue'
import Store from './store'
// ...
new Vue({
router,
store: new Store(router),
render: createElement => createElement(App)
}).$mount('#app')