I'm developing a system whose authentication based on tokens.
I successfully caught and saved the idToken inside the store.js.
Then I create a getter to return this idToken value:
returnToken(state) {
return state.idToken
}
Up to this everything is okay. I can see idToken value in both places (state and getters) through vue developer tools.
Then in the main.js I added a computed property:
export default {
computed: {
returnToken(){
return this.$store.getters.idToken
}
}
}
and Finally I added
axios.defaults.headers.common['Authorization']=this.returnToken
But Authorization header is undefined in the developer tools network.
How do I fix this and How do I know check whether or not it successfully reach to the main.js?
If you have a getter as follows:
returnToken(state){
return state.idToken;
}
Then in the component, access the getter using:
export default {
computed: {
returnToken(){
return this.$store.getters.returnToken; // was getters.idToken
// ^^^^^^^^^^^ --- changed this part
}
}
}
OR, in the component, access the state using:
export default {
computed: {
returnToken(){
return this.$store.state.idToken; // was getters.idToken
// ^^^^^ ------------ changed this part
}
}
}
Related
I am working with Vue 3 composition api and am retrieving weather data via async/await fetch and I get a 200 response and the data in the request within the Chrome Dev Tools.
In the component receiving the data and making the call I have a provide method and then I am injecting the data into another output component. The issue is in the inject component. The value for the injected variable is always null and does not update in the Vue Dev Tools so my data is never output to the screen. I went through the docs and the code is pretty much the same but I can't get it to work. Can anyone see an obvious issue?
Receiving Component
setup () {
async function getCurrentWeather () {
const response = await fetch(`${baseWeatherApiUrl}q=${userInput.value}`);
userInput.value = null;
return weatherData.value = await response.json();
}
const returnedWeatherData = reactive(weatherData);
provide('returnedWeatherData', returnedWeatherData);
return {
getCurrentWeather,
userInput,
weatherData
}
}
output component
setup () {
//Provide default of empty object in case no results exist
const weatherData = inject('returnedWeatherData');
console.log(weatherData) //No output even when making a new request to the weather api
return {
weatherData
}
}
As a separate test I tried to provide/inject hardcoded values found in the docs but still geolocation when injected remains null.
provide('geolocation', {
longitude: 90,
latitude: 135
})
const userGeolocation = inject('geolocation')
console.log(userGeolocation) // Nothing logged
return {
weatherData,
userGeolocation
}
In my case it was importing inject from "#vue/runtime-core" instead of "vue".
Of course provide was imported from "vue".
Just leaving here, maybe it's gonna save someone an hour.
The provide-ed argument should be the ref itself (not wrapped in a reactive()):
// Parent.vue
export default {
setup () {
const weatherData = ref()
// ❌
// const returnedWeatherData = reactive(weatherData);
// provide('returnedWeatherData', returnedWeatherData);
// ✅
provide('returnedWeatherData', weatherData);
}
}
And the child component's console.log() in setup() does not automatically get invoked again. You should wrap that call with watchEffect() so that it does get called upon change to the ref:
// Child.vue
import { inject, watchEffect } from 'vue'
export default {
setup () {
const weatherData = inject('returnedWeatherData')
// ❌
//console.log('new weatherData', weatherData.value)
// ✅
watchEffect(() => {
console.log('new weatherData', weatherData.value)
})
}
}
demo
So I have a plain js file which contains some stuff like functions, one of these is a function that gets a value from 1. localstorage if not present 2. vuex if not present use a default value. These values can be updated thru the whole app in several components which means that components that also are using this value neer te update this value. For now I cant seem to make this part reactive.
helper.js
export const helperFunc = () => {
let value
//dummy
if(checkforlocalstorage){
value = localstorage_value
} else {
value = other_value
}
return value
}
ComponentOne.vue
<template>
<div>{{dynamicValue}}</div>
</template>
<script>
import {helperFunc} from './plugins/helpers.js'
export default {
data () {
return {
}
},
computed: {
dynamicValue : function () {
return helperFunc()
}
},
}
<script>
ComponentTwo.vue
ComponentThree.vue
Update the values here
local or session storage is not a reactive data and vue can't watch them. so you better set the variable in a vuex store, this way the data is reactive and all the components can access it from everywhere in the app.
but there's a catch that you might run into and that is if you refresh the page, all the data in the store get lost and you get the initial values.
but there is one thing you can do, and that is:
save the variable in the localStorage to have it even after the page refresh
initialize the store state value to have the data reactive and accessible through out the app
update the state and localStorage every-time data changes so you can have the reactivity and also latest value in case of a page reload.
Here I show you the basic of this idea:
first you need to setup the store file with the proper state, mutation and action:
export default {
state() {
return {
myVar: 'test',
}
},
mutations: {
UPDATE_MY_VAR(state, value) {
state.myVar = value;
}
},
actions: {
updateMyVar({ commit }, value) {
localStorage.setItem('myVar', value);
commit('UPDATE_MY_VAR', value);
},
initializeMyVar({ commit }) {
const value = localStorage.getItem('myVar');
commit('UPDATE_MY_VAR', value);
}
}
}
then in the beforeCreate or created hook of the root component of your app you'll have:
created() {
this.$store.dispatch('initializeMyVar');
}
this action read the data from a localStorage and initialize the myVar state and you can access that form everywhere like $store.state.myVar and this is reactive and can be watched. also if there is no localStorage and you need a fallback you can write the proper logic for this.
then whenever the data needs to be changed you can use the second action like $store.dispatch('updateMyVar', newUpdatedValue) which updates both the localStorage and the state.
now even with a page reload you get the latest value from the localStorage and the process repeats.
I've got a grid component that I use in many routes in my app. I'd like to persist its state (ie. paging, search param) and restore it when the user comes back to the grid (ie. from editing a row). On the other hand, when the user starts a new flow (ie. by clicking a link) then the page is set to zero and web service is called with the default param.
How can I recognise the user does come back rather then starts a new flow?
When I was researching the problem I've come across the following solutions.
Unfortunatelly they didn't serve me
1/ using router scroll behaviour
scrollBehavior(to, from, savedPosition) {
to.meta.comeBack = savedPosition !== null;
}
It does tell me if the user comes back. Unfortunately the scroll behaviour runs after grid's created and mounted hooks are called. This way I have no place to put my code to restore the state.
2/ using url param
The grid's route would have an optional param. When the param is null then the code would know it's a new flow and set a new one using $router.replace routine. Then the user would go to editing, come back and the code would know they come back because the route param != null. The problem is that calling $router.replace re-creates the component (ie. calling hooks etc.). Additionally the optional param mixes up and confuses vue-router with other optional params in the route.
HISTORY COMPONENT
// component ...
// can error and only serves the purpose of an idea
data() {
return {
history: []
}
},
watch: {
fullRoute: function(){
this.history.push(this.fullRoute);
this.$emit('visited', this.visited);
}
},
computed: {
fullRoute: function(){
return this.$route.fullPath
},
visited: function() {
return this.history.slice(-1).includes(this.fullRoute)
}
}
the data way
save the information in the browser
// component ...
// can error and only serves the purpose of an idea
computed: {
gridData: {
get: function() {
return JSON.parse(local.storage.gridData)
},
set: function(dataObj){
local.storage.gridData = JSON.stringify(dataObj)
}
}
}
//...
use statemanagement
// component ...
// can error and only serves the purpose of an idea
computed: {
gridData: {
get: function() {
return this.$store.state.gridData || {}
},
set: function(dataObj){
this.$store.dispatch("saveGrid", gridData)
}
}
}
//...
use globals
// component ...
// can error and only serves the purpose of an idea
computed: {
gridData: {
get: function() {
return window.gridData || {}
},
set: function(dataObj){
window.gridData = dataObj
}
}
}
I'm trying to use the package Toasted but I'm having a hard time understading how to use it.
I have a package called TreatErrors.js and I call this package to handle all errors from my application based on HTTP code returned by API a restfull API.
TreatErrors.js
import toasted from 'vue-toasted';
export default {
treatDefaultError(err){
let statusCode = err.response.status;
let data = err.response.data;
for(let field in data.errors){
if (data.errors.hasOwnProperty(field)) {
data.errors[field].forEach(message => {
toasted.show(message);
})
}
}
if(statusCode === 401){
toastr.error('Your token has expired. Please logout and login again to retrieve a new token');
}
return null;
},
}
and I'm tryin to call Toasted from within this package but I'm getting vue_toasted__WEBPACK_IMPORTED_MODULE_2___default.a.show is not a function. Any idea how I can use this Toasted inside of my own defined package?
The vue-toasted plugin must be registered with Vue first:
import Toasted from 'vue-toasted';
Vue.use(Toasted); // <-- register plugin
Then, your module could use it via Vue.toasted.show(...):
// TreatErrors.js
export default {
treatDefaultError(err) {
Vue.toasted.show(err.message);
}
}
And your Vue components could also use it via this.$toasted.show(...):
// Foo.vue
export default {
methods: {
showError(err) {
this.$toasted.show(err.message);
}
}
}
Using the vuex-module-decorator I have a authenticate action that should mutate the state.
#Action
public authenticate(email: string, password: string): Promise<Principal> {
this.principal = null;
return authenticator
.authenticate(email, password)
.then(auth => {
const principal = new Principal(auth.username);
this.context.commit('setPrincipal', principal);
return principal;
})
.catch(error => {
this.context.commit('setError', error);
return error;
});
}
// mutations for error and principal
But this fail with the following message:
Unhandled promise rejection Error: "ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access this.someMutation() or this.someGetter inside an #Action?
That works only in dynamic modules.
If not dynamic use this.context.commit("mutationName", payload) and this.context.getters["getterName"]
What I don't understand is that it works well with #MutationAction and async. However I miss the return type Promise<Principal>.
#MutationAction
public async authenticate(email: string, password: string) {
this.principal = null;
try {
const auth = await authenticator.authenticate(email, password);
return { principal: new Principal(auth.username), error: null };
} catch (ex) {
const error = ex as Error;
return { principal: null, error };
}
}
--
At this time I feel blocked and would like to have some help to implement an #Action that can mutate the state and return a specific type in a Promise.
Just add rawError option to the annotation so it becomes
#Action({rawError: true})
And it display error normally. this is because the the library "vuex-module-decorators" wrap error so by doing this you will able to get a RawError that you can work with
You can vote down this answer if you would like because it isn't answering the specific question being posed. Instead, I am going to suggest that if you are using typescript, then don't use vuex. I have spent the past month trying to learn vue /vuex and typescript. The one thing I am committed to is using typescript because I am a firm believer in the benefits of using typescript. I will never use raw javascript again.
If somebody would have told me to not use vuex from the beginning, I would have saved myself 3 of the past 4 weeks. So I am here to try and share that insight with others.
The key is Vue 3's new ref implementation. It is what really changes the game for vuex and typescript. It allows us to not have to rely on vuex to automatically wrap state in a reactive. Instead, we can do that ourselves with the ref construct in vue 3. Here is a small example from my app that uses ref and a typescript class where I was expecting to use vuex in the past.
NOTE1: the one thing you lose when using this approach is vuex dev tools.
NOTE2: I might be biased as I am ported 25,000 lines of typescript (with 7000 unit tests) from Knockout.js to Vue. Knockout.js was all about providing Observables (Vue's ref) and binding. Looking back, it was kind of ahead of its time, but it didn't get the following and support.
Ok, lets create a vuex module class that doesn't use vuex. Put this in appStore.ts. To simplify it will just include the user info and the id of the club the user is logged into. A user can switch clubs so there is an action to do that.
export class AppClass {
public loaded: Ref<boolean>;
public userId: Ref<number>;
public userFirstName: Ref<string>;
public userLastName: Ref<string>;
// Getters are computed if you want to use them in components
public userName: Ref<string>;
constructor() {
this.loaded = ref(false);
initializeFromServer()
.then(info: SomeTypeWithSettingsFromServer) => {
this.userId = ref(info.userId);
this.userFirstName = ref(info.userFirstName);
this.userLastName = ref(info.userLastName);
this.userName = computed<string>(() =>
return this.userFirstName.value + ' ' + this.userLastName.value;
}
}
.catch(/* do some error handling here */);
}
private initializeFromServer(): Promise<SomeTypeWithSettingsFromServer> {
return axios.get('url').then((response) => response.data);
}
// This is a getter that you don't need to be reactive
public fullName(): string {
return this.userFirstName.value + ' ' + this.userLastName.value;
}
public switchToClub(clubId: number): Promise<any> {
return axios.post('switch url')
.then((data: clubInfo) => {
// do some processing here
}
.catch(// do some error handling here);
}
}
export appModule = new AppClass();
Then when you want to access appModule anywhere, you end up doing this:
import { appModule } from 'AppStore';
...
if (appModule.loaded.value) {
const userName = appModule.fullName();
}
or in a compositionApi based component. This is what would replace mapActions etc.
<script lang="ts">
import { defineComponent } from '#vue/composition-api';
import { appModule } from '#/store/appStore';
import footer from './footer/footer.vue';
export default defineComponent({
name: 'App',
components: { sfooter: footer },
props: {},
setup() {
return { ...appModule }
}
});
</script>
and now you can use userId, userFirstName, userName etc in your template.
Hope that helps.
I just added the computed getter. I need to test if that is really needed. It might not be needed because you might be able to just reference fullName() in your template and since fullName() references the .value variables of the other refs, fullName might become a reference itself. But I have to check that out first.
I sugest this simple solution, work fine for me 👌:
// In SomeClassComponent.vue
import { getModule } from "vuex-module-decorators";
import YourModule from "#/store/YourModule";
someMethod() {
const moduleStore = getModule(YourModule, this.$store);
moduleStore.someAction();
}
If the action has parameters, put them.
Taken from: https://github.com/championswimmer/vuex-module-decorators/issues/86#issuecomment-464027359