Saving data to local storage using vuex persisted state - vuejs2

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.

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

Navigation component not getting re rendered with stage change in Vue3

When a user updates their username in the EditAccount component, the username is updated in the EditAccount component and in vuex store but not in the Navigation component even though stage change is updated to the new user name.
The problem is that the user is seing thier old user name in Navigation component and a updated user name in the EditAccount component and they don't match.
How can I Re render the Navigation component with the new user name?
Below is the the code for user the data in the Navigation component.
Store vuex: index.js
const store = createStore({
// strict: true,
state: {
user: null,
authIsReady: false,
//
// current category
playlistCategory: null,
},
//
getters: {
getUser(state) {
return state.user;
},
},
mutations: {
//
// update playlist category
updatePlaylistCategory(state, payload) {
state.playlistCategory = payload;
},
//
//
setUser(state, payload) {
state.user = payload;
},
//
setAuthIsReady(state, payload) {
state.authIsReady = payload;
},
//
},
actions: {
async editUser(context, payload) {
const { displayNewName, displayNewEmail } = payload;
await updateUserDetails(displayNewName, displayNewEmail);
// get current user
const responseUser = await user;
// set user state
context.commit('setUser', responseUser);
},
},
NavBar.vue
// vue3 and composition api
setup() {
// store
const store = useStore()
//
const { error, logout, isPending } = useLogout()
const router = useRouter()
//
// getters
const user = computed(() => {
return store.getters.getUser.displayName
})
Try adding set and get property:
const user = computed({
get: store.state.user,
set: (val) => store.state.user = val
});
Try using a getter instead acessing the value directly in the state
Getter for user:
export function getUser(state){
return state.getUser
}
and in the component import the getter like this:
<script>
import {mapGetters} from 'vuex'
export default {
computed: {
...mapGetters('*theStoreName*',['getUser'])
},
watch: {
getUser: function(){
//Should be possible to see when the getUser changes here
console.log(this.getUser)
}
}
}
</script>
Note: You have theStoreName for the store name you're using
Maybe the problem is that the store name is missing, or when you did store.state.user you're acessing the store? If it is it, then you should try to inform the variable you're trying to access, like If it is, like store.state.user.name, with the getter it would be: getUser.name

Vue3 / Vuex State is empty when dispatching action inside of lifecycle hook inside of test

We're using the composition API with Vue 3.
We have a Vuex store that, amongst other things, stores the currentUser.
The currentUser can be null or an object { id: 'user-uuid' }.
We're using Vue Test Utils, and they've documented how to use the store inside of tests when using the Composition API. We're using the store without an injection key, and so they document to do it like so:
import { createStore } from 'vuex'
const store = createStore({
// ...
})
const wrapper = mount(App, {
global: {
provide: {
store: store
},
},
})
I have a component and before it is mounted I want to check if I have an access token and no user currently in the store.
If this is the case, we want to fetch the current user (which is an action).
This looks like so:
setup() {
const tokenService = new TokenService();
const store = useStore();
onBeforeMount(async () => {
if (tokenService.getAccessToken() && !store.state.currentUser) {
await store.dispatch(FETCH_CURRENT_USER);
console.log('User: ', store.state.currentUser);
}
});
}
I then have a test for this that looks like this:
it('should fetch the current user if there is an access token and user does not exist', async () => {
localStorage.setItem('access_token', 'le-token');
await shallowMount(App, {
global: {
provide: {
store
}
}
});
expect(store.state.currentUser).toStrictEqual({ id: 'user-uuid' });
});
The test fails, but interestingly, the console log of the currentUser in state is not empty:
console.log src/App.vue:27
User: { id: 'user-uuid' }
Error: expect(received).toStrictEqual(expected) // deep equality
Expected: {"id": "user-uuid"} Received: null
Despite the test failure, this works in the browser correctly.
Interestingly, if I extract the logic to a method on the component and then call that from within the onBeforeMount hook and use the method in my test, it passes:
setup() {
const tokenService = new TokenService();
const store = useStore();
const rehydrateUserState = async () => {
if (tokenService.getAccessToken() && !store.state.currentUser) {
await store.dispatch(FETCH_CURRENT_USER);
console.log('User: ', store.state.currentUser);
}
};
onBeforeMount(async () => {
await rehydrateUserState();
});
return {
rehydrateUserState
};
}
it('should fetch the current user if there is an access token and user does not exist', async () => {
localStorage.setItem('access_token', 'le-token');
await cmp.vm.rehydrateUserState();
expect(store.state.currentUser).toStrictEqual({ id: 'user-uuid' });
});
Any ideas on why this works when extracted to a method but not when inlined into the onBeforeMount hook?

Passing OAuth token from one Vuejs component to another

I get OAuth token after successful OAuth login in a SuccessOAuth.vue component. I get the token details as follows:
checkforTokens(){
const queryString = this.$route.query;
console.log(queryString);
const token = this.$route.query.accessToken
console.log(token);
const secret = this.$route.query.tokenSecret
console.log(secret);
this.tokens.token = token;
this.tokens.secret = secret;
}
},
beforeMount() {
this.checkforTokens();
}
Now I want to use this token in another component apiCalls.vue where I use this token details to use call the API methods.
<script>
...
methods:{
getProductDetails() {
console.log("==========================================");
console.log(".. Get Product details....");
axios
.get("/auth/getShpDetails", {
params: {
token: this.tokens.token
}
})
.then(response => {
const productInfo = response.data;
console.log("Product info :" + productInfo);
});
},
}
</script>
How do I pass the token details from SuccessOAuth component to apiCalls. I tried using props method but I wasn't able to get the token value to the script tag, not sure about other methods used to pass i.e using $emit and using vuex. Please suggest the best way and the right solution for the problem.
As suggested by #Nishant Sham, I am just modifying the action method in index.js as seen below:
import Vue from 'vue'
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
token: ''
},
getters: {
getToken(state){
return state.token;
}
},
mutations: {
setToken(state, tokenValue){
state.token = tokenValue;
}
},
actions: {
setToken({commit}, tokenValue){
commit("setToken", tokenValue);
}
}
});
In your vue component you call getters and setters as follows:
<script>
//Set token value
var token = "dwe123313e12";//random token value assigned
this.$store.commit("setToken", token);
.....
//Get token value
var getToken = this.$store.getters.getToken;
</script>
You can keep your token inside Localstorage or cookies. And use as per your need. Here is the sample code for this:
const token = 'token'
export function getToken() {
return localStorage.getItem(token)
}
export function setToken(tokenData) {
return localStorage.setItem(token, tokenData)
}
export function removeToken() {
return localStorage.removeItem(token)
}
you can use Vuex for state management. Here is an article
One way to do this could be vuex
in the root, store create a token field and make one getter that you can call from any vue component and on any life cycle hook..
The second way can be that you set the token to localStorage and get/use it wherever you need it
I would prefer the vuex method that way it ensures a single source of truth...
Here is how to use vuex store
First of all install vuex depending on the vue version you are using, Generally, for the vue3 it is advisable to use npm i vuex#next
Create a Store folder inside your src folder and in there add the index.js with the following code
import { createStore } from "vuex";
import axios from "axios"; // I Use axios for making API CALLS hence this pkg
const store = createStore({
state() {
return {
token: null,
};
},
});
export default store;
This is the basic store and state of you app for now.
Lets start adding Actions first because actions are the async code used for making the API call and get the data from server
actions: {
async login(context, payload) {
try {
const result = await axios({
method: "POST",
url: "auth/login",
data: {
email: payload.email,
password: payload.password,
},
});
//If the Request Successed with Status 200
if (result.status === 200) {
//A: Extract the Token
const token = result.data.token;
//B. Token to LocalStorage Optional if you wish to set it to localstorgae
localStorage.setItem("token", token);
//c: UPDATE THE STATE by calling mutation
context.commit("setToken", {
token,
});
}
} catch (err) {
console.log(err);
}
},
},
Next step as you might have guessed adding mutation, which is used for updating your app state..
mutations: {
setToken(state, token) {
state.token = token;
},
},
Last the getter which you shall use to fetch the data either as computed inside your app components this is the
getters: {
getToken(state) {
return state.token;
},
},
Finally after all of this you index.js should look something like this
import { createStore } from "vuex";
import axios from "axios";
const store = createStore({
state() {
return {
token: null,
};
},
actions: {
async login(context, payload) {
try {
const result = await axios({
method: "POST",
url: "auth/login",
data: {
email: payload.email,
password: payload.password,
},
});
//If the Request Successed with Status 200
if (result.status === 200) {
//A: Extract the Token
const token = result.data.token;
//B. Token to LocalStorage Optional if you wish to set it to localstorgae
localStorage.setItem("token", token);
//c: UPDATE THE STATE by calling mutation
context.commit("setToken", {
token,
});
}
} catch (err) {
console.log(err);
}
},
},
mutations: {
setToken(state, token) {
state.token = token;
},
},
getters: {
getToken(state) {
return state.token;
},
},
});
export default store;
NOTE - This is a General representation of how the code for vuex should looks like there are a ton of other way to achive the same result, depending on you project requirment
The above code is not a final code, as it will need to be adjusted as per your test/example/project requirement

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