Persisted state from VueX and NuxtJS - vuex

I use vuex-persistedstate package (https://github.com/robinvdvleuten/vuex-persistedstate) to persist data state on browser.
I use Adonuxt (a mix between NuxtJS and AdonisJS).
In VueX actions, I have this action:
nuxtClientInit ({commit}) {
// I want get here state auth saved by persistedstate package
}
This action is called by plugin:
localstorage.js
export default async (context) => {
await context.store.dispatch('nuxtClientInit', context)
}
nuxt.js plugin (config)
{
src: '~/plugins/localstorage.js',
ssr: false
}
I want get state to configure Axios with the user token:
this.$axios.setToken(auth.jwt.token, 'Bearer')
I have the impression nuxtClientInit() is called before persistedstate package, so state.auth is null but it can observable in console:

I've used https://www.npmjs.com/package/vuex-persist to persist data from Vuex.

csr+ssr cookie
You can choose any one of the below library
1 .vuex-persistedstate
2 .vuex-persist
vuex-persistedstate usage
https://www.npmjs.com/package/vuex-persistedstate
plugins/persistedstate.js
import createPersistedState from 'vuex-persistedstate'
import * as Cookies from 'js-cookie'
import cookie from 'cookie'
export default ({store, req, isDev}) => {
createPersistedState({
key: 'your_key',
paths: ['state1', 'state2',...so_on],
storage: {
getItem: (key) => process.client ? Cookies.getJSON(key) : cookie.parse(req.headers.cookie||'')[key],
setItem: (key, value) => Cookies.set(key, value, { expires: 365, secure: !isDev }),
removeItem: (key) => Cookies.remove(key)
}
})(store)
}
nuxt.config.js
plugins: [
{ src: '~plugins/persistedstate.js' }
]
vuex-persist
https://www.npmjs.com/package/vuex-persist
// ~/plugins/vuex-persist.js
import * as Cookies from 'js-cookie'
import cookie from 'cookie'
import VuexPersistence from 'vuex-persist'
export default ({ store, req, isDev }) => {
new VuexPersistence({
key:'test',
reducer: (state) => ({}),
restoreState: (key, storage) =>process.client ? Cookies.getJSON(key) : cookie.parse(req.headers.cookie||'')[key],
saveState: (key, state, storage) =>
Cookies.set(key, value, { expires: 365, secure: !isDev }),
}).plugin(store);
}
nuxt.config.js
plugins: [
{ src: '~plugins/vuex-persist.js' }
]

In my case, I made a mistake in specifying the directory.
root/
 ├ src/
 ├ pages/
.
.
 ├ src/
   └ plugins/
     └ localstorage.js/
In the above directory, you must specify as follows.
In nuxt.config.js
{src:'~/src/plugins/localstorage.js', srr: false}

Related

How to Implement nuxtServerInit Action to load data from server-side on the initial load in Pinia (Nuxt3)

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

Saving data to local storage using vuex persisted state

I'm currently using this plugin vuex-persistedstate
and I would like to use it with Vuex module of my Nuxt app.
Basically I have a login module if success, then store the authToken coming from the response to localStorage
Here's my code:
import axios from "axios";
import createPersistedState from 'vuex-persistedstate';
export const state = () => ({
signInAttrs: {
email: "",
password: ""
},
authToken: ""
});
export const mutations = {
SET_AUTH_TOKEN(state, token) {
state.authToken = token;
createPersistedState({
key: 'admin-auth-key',
paths: [],
reducer: state => ({
authToken: '123123123'
})
})(store);
}
};
export const actions = {
signInAdmin({ commit }, context) {
return axios.post(`${process.env.BASE_URL}/sign_in`, {
email: context.email,
password: context.password
}).then(response => {
commit('SET_AUTH_TOKEN', response.data.headers.token);
}).catch(error => {
console.log(`failed ${error}`);
});
}
};
export const getters = {
signInAttrs(state) {
return state.signInAttrs;
},
authToken(state) {
return state.authToken;
}
};
Inside the mutations there's SET_AUTH_TOKEN that receives the token as the parameter from API. How can I save it to localStorage?
I think you're misunderstanding usage of vuex-persistedstate. Once you add it to Store plugins (plugins: [createPersistedState()]), it automatically updates localStorage variable vuex with a copy of your store on each mutation commit (see example). So your token should be inside vuex.authToken in localStorage already.
If you want to simply store a variable with custom name you can do it without plugins: localStorage.setItem('key', 'value'). See this question.

vuex persisted state not working with vue router navigation guard

I've added vuex-persistedstate as defined in documentation. (confirmed and working)
export default new Vuex.Store({
modules: {
auth,
},
plugins: [VuexPersistedState()]
});
I've set up a router navigation guard to redirect to home page on login
/* Auth based route handler */
router.beforeEach((to, from, next) => {
if (to.meta.hasOwnProperty('requiresAuth')) {
if (to.meta.requiresAuth === true) {
let isAuthenticated = authStore.getters.isAuthenticated
if (isAuthenticated(authStore.state) === true) {
next()
} else {
next({name: 'login'})
}
} else {
let isAuthenticated = authStore.getters.isAuthenticated
console.log(authStore.state)
if (isAuthenticated(authStore.state) === true) {
next({name: 'home'})
} else {
next()
}
}
} else {
next()
}
})
The vuex persistedstate restores store from local storage but not before navigation guard!
I can provide any necessary part of the source code for evaluation. Please comment your request as needed. Experimental solutions are also welcome. This is just a personal training application for me!
Help is appreciated!
I know this is probably of no use to #Vaishnav, but as I wen't down the same rabbit hole recently I figured I'd post a workaround I found for this here as this was the only post I found that asked this issue specifically.
in your store/index.js You need to export both the function and the store object.
Change this :
export default() => {
return new vuex.Store({
namespaced: true,
strict: debug,
modules: {
someMod
},
plugins: [createPersistedState(persistedStateConfig)]
});
};
to:
const storeObj = new vuex.Store({
namespaced: true,
strict: debug,
modules: {
someMod
},
plugins: [createPersistedState(persistedStateConfig)]
});
const store = () => {
return storeObj;
};
export {store, storeObj}
Then also, as you have now changed the way you export the store you will also need to change the way it's imported.
Everywhere in your app you've imported the store ie: in main.js -> import store from '#/store' excluding the router.js you will need to change to import {store} from '#/store'
And in your router.js just import {storeObj} from '#/store' and use that instead of store in your router guard ie: storeObj.getters['someMod/someValue']
I found a working example.
As the router isn't a component.
In router config file -
import authStore from '#/stores/auth/store'
Instead -
import store from '#/store'
in navigation guard, I replaced
let isAuthenticated = authStore.getters.isAuthenticated
if(isAuthenticated(authstore.state) === true){...}
with -
let isAuthenticated = store.getters['auth/isAuthenticated']
if(isAuthenticated === true){...}
And now this works like charm...

How to attach axios / axios interceptor to Nuxt globally ?

how would i go about attaching axios / axios interceptor globally to nuxt (so its available everywhere), same how i18n is attached ?
The idea is that i would like to have a global axios interceptor that every single request goes through that interceptor.
Thanks
you can create a plugin called axios (/plugins/axios.js)
import Vue from 'vue';
import axios from 'axios';
axios.interceptors.request.use((config) => {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
Vue.use(axios);
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
thats all, your interceptor is now working globally
It's hidden in the documentation - https://nuxtjs.org/docs/2.x/directory-structure/plugins
See number 3 of the first photo:
// plugins/axios.js
export default function ({ $axios, redirect }) {
$axios.onError(error => {
if (error.response.status == 404) {
redirect('/sorry')
}
})
}
then define this in nuxt.config.js
module.exports = {
//....
plugins: [
'~/plugins/axios',
],
//....
};
Maybe will be helpful for someone.
It just sets the lang parameter for every request.
Сreate a plugin called axios (/plugins/axios.js). Put it there:
export default function ({ $axios, app, redirect }) {
$axios.onRequest(config => {
config.params = config.params || {}; // get existing parameters
config.params['lang'] = app.i18n.locale;
})
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
})
}
Add in nuxt.config.js:
module.exports = {
plugins: [
'~/plugins/axios'
]
};
Create a new module, call it request.js for example.
import axios from 'axios'
const instance = axios.create({
baseURL: 'http://example.org' // if you have one
})
// Put all interceptors on this instance
instance.interceptors.response.use(r => r)
export default instance
Then simply import that instance whenever you need it and use it like it was a normal axios instance:
import request from './request'
await request.get('/endpoint')
// or use promises
request.get('/endpoint').then(data => data)
If you really need it globally you can use the following code in your entry point of the application:
import request from './request'
global.request = request
// use it:
await request.get('example.org')
Or you can add it to the vue protype
Vue.prototype.$request = request
// in your component:
this.$request.get()
I'd advice against it though.

I want to use window.localStorage in Vuex in Nuxt.js

I developing nuxt.js app. And point is login & logout.
We will develop a login to the JWT system.
You must remain logged in at vuex.
However, when I refresh the page, vuex is initialized.
I've read git vuex-persistedstate , but it's hard to understand just how to initialize and set it.
What is the best way to develop a login system in nuxt.js?
Thanks.
Using vuex-persisted state would be the best use case for your scenario.
I will walk you through the process of using vuex-persisted state.
Open command line, cd to your project directory, then enter npm install --save vuex-persistedstate. This will install vuex-persistedstate into your project dependencoes.
Now in your store.js file or wherever your defined your vuex store, add the vuex-persistedstate plugin
import createPersistedState from "vuex-persistedstate";
import * as Cookie from "js-cookie";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
user: {
name: "john doe",
age: " 16",
},
loggedIn: false,
hobbies: ["eating", "partying"],
},
plugins: [
createPersistedState({
paths: ["user", "loggedIn"],
getState: (key) => Cookie.getJSON(key),
setState: (key, state) =>
Cookie.set(key, state, { expires: 1, secure: false }),
}),
],
});
You also need js-cookie package which makes handling cookies easier. Use npm install --save js-cookie.
The paths property says which parts of the state to persist, in our case save as cookies.If no path property is given, then the whole state is persisted
From the above example we have mentioned the paths paths: ['user', 'loggedIn'], so only user and loggedIn properties of the state are saved in cookies not hobbies.
In case you are using modules in your store, the way of defining the pats to persist would be as follows:
import createPersistedState from "vuex-persistedstate";
import * as Cookie from "js-cookie";
import myModule from "./myModule";
import myAnotherModule from "./myAnotherModule";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
user: {
name: "john doe",
age: " 16",
},
loggedIn: false,
hobbies: ["eating", "partying"],
},
modules: {
myModule,
myAnotherModule,
},
plugins: [
createPersistedState({
paths: ["user", "loggedIn", "myModule.<nameOfThePropretyInState>"],
getState: (key) => Cookie.getJSON(key),
setState: (key, state) =>
Cookie.set(key, state, { expires: 1, secure: false }),
}),
],
});
In your paths you will refer to the module's property in the state you want to persist. In the above example, the property of the state that you mention of myModule is persisted. myAnotherModule state is not saved since it is not mentioned in the paths.
That's it . If you want to customize the way you use vuex-persisted state and js-cookie, have a look at their documentation.
If you want to check whether your desired state is saved in cookies then you can console log your cookies like this: console.log(document.cookie in your App.vue created() lifecycle hook
I have used vuex-persist package instead, very easy to get it up and running. This works for SSR too.
import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersist from 'vuex-persist'
import actions from './actions'
import mutations from './mutations'
import getters from './getters'
Vue.use(Vuex)
let vuexLocalStorage = null;
if (process.browser) {
vuexLocalStorage = new VuexPersist({
key: 'vuex', // The key to store the state on in the storage provider.
storage: window.localStorage, // or window.sessionStorage or localForage
})
}
export function createStore() {
return new Vuex.Store({
state: {
},
actions,
mutations,
getters,
plugins: process.browser ? [vuexLocalStorage.plugin] : []
})
}
Just make sure to condition everything to just run in the browser
Better to use cookies for saving authorization token, look at this nuxt module
https://github.com/microcipcip/cookie-universal/tree/master/packages/cookie-universal-nuxt
Here sample on vuex store module to set cookie
//call async ajax request to get UUID
const uuidReq = await dispatch('getUUID')
if (uuidReq.hasOwnProperty('meta')) {
commit('setState', {
uuid: uuidReq.meta.links.me.meta.id,
isLogin: true
})
// calculate expires
const expDate = new Date()
expDate.setTime(expDate.getTime() + (state.accExpKey - 0.3) * 1000)
const expDate2 = new Date()
expDate2.setTime(expDate.getTime() + 2592000 * 1000)
const options = {
path: '/',
expires: expDate
}
const options2 = {
path: '/',
expires: expDate2
}
const cookieList = [{
name: 'g_isLogin',
value: true,
opts: options2
},
{
name: 'g_accKey',
value: state.accKey,
opts: options
},
{
name: 'g_refKey',
value: state.refKey,
opts: options2
},
{
name: 'g_userUUID',
value: uuidReq.meta.links.me.meta.id,
opts: options
}
]
this.$cookies.setAll(cookieList)
}
Here sample implementation on custom Nuxt middleware check existing cookie then inject them into vuex state
export default function({ store, route, redirect, app }) {
const isLogin = app.$cookies.get('g_isLogin') === 'true'
const accKey = app.$cookies.get('g_accKey') || ''
const refKey = app.$cookies.get('g_refKey') || ''
const userUUID = app.$cookies.get('g_userUUID') || ''
// console.warn('authenticated isLogin:', isLogin)
// If the user authenticated
if (isLogin) {
store.commit('user/setState', {
isLogin: isLogin,
accKey: accKey,
refKey: refKey,
uuid: userUUID
})
} else {
return redirect('/?prevURL=' + route.path)
}
}
I would strongly recommend using cookies over localStorage with nuxt and the vuex store. Using a package such as univeral-cookie and the built-in nuxtServerInit action, you can populate both client and server stores by reading the cookies on the initial request from the server. You may be limited in the amount of data you can store with cookies but if you implement a RESTful-like API and store ids in your cookies whenever possible, you can server-side fetch that data to populate the full stack store thereby setting yourself up very well in cases where the user refreshes the page. I found it very handy with auth tokens, too, which expire on their own cookie-related behavior and hence wont exist in the store (or its mutation handled decoded data) in cases where the page refreshes.
for using vuex-persistedstate in nuxt both client and server , follow these steps.
For example consider you have a Vuex Module user and you want to persist it . even if you refresh or route to another page.
const user = {
namespaced: true,
state: () => ({
name: 'geeekfa'
}),
mutations: {
name(state, name) {
state.name = name;
},
},
getters: {
name: (state) => {
return state.name;
},
}
}
export default user
install vuex-persistedstate
npm install --save vuex-persistedstate
install cookie & js-cookie
npm install --save cookie js-cookie
after that your package.json is like :
"dependencies": {
...
"cookie": "^0.3.1",
"js-cookie": "^2.2.1",
"vuex-persistedstate": "^4.0.0-beta.3",
...
}
create persistedState.js in ~/plugin/persistedState.js
// persistedState.js
import createPersistedState from 'vuex-persistedstate'
import * as Cookies from 'js-cookie'
import cookie from 'cookie'
export default ({ store, req }) => {
createPersistedState({
paths: ['user'], // your vuex module name
storage: {
getItem: (key) => {
if (process.server) {
const parsedCookies = cookie.parse(req.headers.cookie)
return parsedCookies[key]
} else {
return Cookies.get(key)
}
},
setItem: (key, value) =>
Cookies.set(key, value, { expires: 365, secure: false }),
removeItem: key => Cookies.remove(key)
}
})(store)
}
add this plugin to nuxt.config.js
plugins: [
...
{ src: '~/plugins/persistedState.js' }
...
],
this is enough ! you can persist user module even after refresh in both client and server side . there is no need to change ~/store/index.js file