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
Related
I'm building a vuejs 3 application with composition API.
I have 2 stores: a userStore for holding userid, jwt and similar stuff (that gets populated upon login) and a dataStore that holds data related to the user (populated when user does operations).
When a user logs in successfully, she is redirected to a page containing user data.
The login page uses the userStore and the data page uses the dataStore. The dataStore needs the user's id and jwt.
This method is called upon login:
const submitlogin = async () => {
try {
const response = await postData.post('/user/a/login', {
email: form.email,
password: form.password,
})
if (response) {
userStore.loggedIn = true
// first get the jwt
userStore.getJWT()
// then go to the next page where jwt is required
router.push({
name: 'operation',
params: { sens: 'depense', quand: 'maintenant' },
})
}
} catch (error) {
console.log (error)
}
}
I import the userStore into the dataStore:
// dataStore
import { defineStore } from 'pinia'
import { useUserStore } from '#/stores/userStore.js'
actions: {
async getAccounts(id, month, year) {
const user = useUserStore
// getData is an [axios create function][1]
getData.defaults.headers.common['__authorization__'] = user.jwt
getData.get(`/use/b/comptes/${id}/${month}/${year}`).then((response) => {
// cut because irrelevant here
}
Then, on the first after login:
// data view
import { useUserStore } from '../stores/userStore'
import { useDataStore } from '#/stores/dataStore'
const dataStore = useDataStore()
const userStore = useUserStore()
onMounted(() => {
dataStore.getAccounts()
})
However, the autorization header is undefined only at this first call. If I further navigated to other views where I import the dataStore user.jwt is defined.
It seems that the dataStore is mounted correclty, but its state isn't available yet at the moment I call it.
Solved!
I changed the dataStore so that userStore is defined not within the function, but right after import.
Kind of logical since the getAccounts function is async, so the definition of user.jwt also was.
import { defineStore } from 'pinia'
import { getData } from '#/composables/useApi'
import { sumBy } from 'lodash'
import { useUserStore } from '#/stores/userStore.js'
// put this here, not within the async action !
const userStore = useUserStore()
actions: {
async getAccounts(id, month, year) {
getData.defaults.headers.common['__authorization__'] = userStore.jwt
getData.get(`/use/b/comptes/${id}/${month}/${year}`).then((response) => {
// cut because irrelevant here
}
My Code:
export const useMenuStore = defineStore("menuStore", {
state: () => ({
menus: [],
}),
actions: {
async nuxtServerInit() {
const { body } = await fetch("https://jsonplaceholder.typicode.com/posts/1").then((response) => response.json());
console.log(body);
this.menus = body;
resolve();
},
},
});
NuxtServerInit is not working on initial page render on nuxt js vuex module mode.Anyone know this error please help me.
NuxtServerInit is not implemented in Pinia, but exists a workaround.
Using Pinia alongside Vuex
// nuxt.config.js
export default {
buildModules: [
'#nuxtjs/composition-api/module',
['#pinia/nuxt', { disableVuex: false }],
],
// ... other options
}
then Include an index.js file inside /stores with a nuxtServerInit action which will be called from the server-side on the initial load.
// store/index.js
import { useSessionStore } from '~/stores/session'
export const actions = {
async nuxtServerInit ({ dispatch }, { req, redirect, $pinia }) {
if (!req.url.includes('/auth/')) {
const store = useSessionStore($pinia)
try {
await store.me() // load user information from the server-side before rendering on client-side
} catch (e) {
redirect('/auth/login') // redirects to login if user is not logged in
}
}
}
}
In Nuxt2, the Nuxt will run the code in nuxtServerInit() of store/index.js on the server-side to boot the app.
However, in Nuxt3, there is no specific place to write the boot code, you can write the boot code anywhere instead of in nuxtServerInit() of store/index.js.
It might be helpful, especially when you need to send a request before boosting the app.
your pinia file may define like following:
store/menu.js
import { defineStore } from 'pinia';
export const useMenuStore = defineStore('menuStore', {
state: () => ({
_menus: [],
}),
getters: {
menus() {
return this._menus;
}
},
actions: {
async boot() {
const { data } = await useFetch('https://jsonplaceholder.typicode.com/posts/1');
this._menus = data;
}
}
});
Then, create a plugin which named as *.server.[ts|js], for example init.server.js
(.sever.js tail will let the file only run in server side)
plugins/init.server.js
import { defineNuxtPlugin } from '#app';
import { useMenuStore } from '~/store/menu.js';
export default defineNuxtPlugin(async (nuxtApp) => {
const menu = useMenuStore(nuxtApp.$pinia);
await menu.boot();
});
nuxt.config.js
modules: [
'#pinia/nuxt',
],
There is an entire example of SSR Nuxt3 with authorization that may help
I tried many things mentioned on the portal but nothing seems to work for me so posting here for some work-around.
I have 2 modules within my Nuxtjs application folder store\modules: ModuleA and ModuleB. For some verification in ModuleB I would like to access the state from ModuleA but for some reason, it's failing.
I tried rootScope, import etc but it did not work for me.
My state/modules/ModuleA.js:
export const state = () => ({
eventType: 'MyEvent',
})
export const mutations = {
eventTypePopulator (state, event) {
state.eventType = event
},
}
My state/modules/ModuleB.js:
export const state = () => ({
input: {}
})
export const mutations = {
jsonPreparation ({state, rootState}, payload) {
console.log(rootState.eventType)
// console.log($store.state.ModuleA.eventType)
}
}
I tried to import ModuleA into ModuleB and use it but that also did not work for me. Can someone please help me how can I access the state from one Module in another Module within Nuxtjs/Vuejs
As shown in the API reference, rootState is available in actions and getters.
It did not found any way of using it directly into a mutation.
Meanwhile, this can be achieved by passing it as a param to the mutation like this
ModuleB.js
const mutations = {
NICE_TASTY_MUTATION: (_state, { rootState, payload }) => {
// _state is not used here because it's moduleB's local state
rootState['moduleA'].eventType = payload
},
}
const actions = {
async myCoolAction({ commit, rootState }, { ...}) {
commit('NICE_TASTY_MUTATION', {
rootState,
payload: 'some-stuff'
})
}
}
And this could be called in a .vue file with something like this
methods: {
...mapActions('ModuleB', ['myCoolAction']),
}
...
await this.myCoolAction()
PS: IMO the convention is more to name the file module_b.js.
I am trying to use an instance of an object in a boot file where the instance is created in another boot file. The documentation [0] talks about using an object instance from a boot file and it works fine when using the instance in a component. I would like to access the instance in another boot file.
First boot file that creates the instance looks like this:
import { AuthService } from '../authorization/AuthService';
let oidc = null
export default ({ router, store, Vue }) => {
const OIDC = new AuthService();
router.beforeEach((to, from, next) => {
const allowAnonymous = to.matched.some(record => record.meta.allowAnonymous)
if (allowAnonymous) {
next()
} else {
var isAuthenticated = OIDC.isAuthenticated()
if (isAuthenticated) {
next()
} else {
OIDC.signIn()
}
}
})
Vue.prototype.$oidc = OIDC
oidc = OIDC
}
export { oidc }
And I am trying to use the oidc instance in another boot file like this:
import oidc from "boot/oidc-service"
import axios from 'axios'
let axiosInstance = null;
export default ({ app, router, store, Vue }) => {
const AxiosInstance = axios.create({
baseURL: window.env.BASE_URL
})
AxiosInstance.interceptors.request.use(function (config) {
return oidc.getAccessToken().then(token => {
config.headers.Authorization = `Bearer ${token}`
return config
})
}, (error) => {
return Promise.reject(error)
})
Vue.prototype.$axios = AxiosInstance
axiosInstance = AxiosInstance
}
export { axiosInstance }
I import them in the following order:
boot: [
'oidc-service',
'axios',
...
If I export the class instead of the instance, I can instantiate it and code works as expected. I would like for the oidc object to be a singleton however.
How can I use the instance of oidc in my axios setup?
[0] https://quasar.dev/quasar-cli/boot-files#Accessing-data-from-boot-files
Unless I'm missing something... if oidc is not null, return it, otherwise continue with the initialization:
import { AuthService } from '../authorization/AuthService';
let oidc = null
export default ({ router, store, Vue }) => {
if(oidc !== null) return oidc;
// else continue...
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 = () => ({})