I have the following components:
Parent:
<template>
<Child path="instance.json"
v-bind:authenticated="authenticated"
v-bind:authenticator="authenticator"
/>
</template>
<script>
import { getAuthenticator } from '../auth';
export default {
data() {
return {
authenticated: false,
authenticator: null
};
},
beforeMount: async function () {
this.authenticator = getAuthenticator()
this.checkAccess();
},
methods: {
checkAccess() {
this.authenticated = this.authenticator.isAuthenticated();
},
async login() {
this.checkAccess();
await this.authenticator.signIn();
this.checkAccess();
}
}
};
</script>
Child:
<template>
<div id="swagger-ui"></div>
</template>
<script>
import swagger from "swagger-ui-dist";
import "swagger-ui-dist/swagger-ui.css";
export default {
props: ["path", "authenticated", "authenticator"],
mounted: async function() {
if (this.authenticated) {
let token = (await this.authenticator.getToken()).accessToken;
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: "#swagger-ui",
onComplete: function() {
ui.preauthorizeApiKey("token", token);
}
});
} else {
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: "#swagger-ui"
});
}
}
};
</script>
In the parent component, when the login method is called, the authenticated variable changes to true. Since authenticated is passed as a prop to the Child component, I'd expect the Child to be refreshed whenever authenticated is changed. However, the Child does not refresh.
I think that the problem might be caused by the fact that I am not using authenticated in the template of the child at all. Instead, I'm using it only in the mounted hook. In my case, I have no use for authenticated in the template.
I tried two solutions:
calling this.$forceUpdate() in the login method of Parent - that didn't work at all (nothing changed)
Adding :key to the Child, and changing the key each time the login is called - this works, however, it's a bit hacky. I'd like to understand how to do that properly.
what you need is to use a watcher.
Actually, your code is only run once (when de component is mounted), not at each prop change.
<template>
<div id="swagger-ui"></div>
</template>
<script>
import swagger from 'swagger-ui-dist';
import 'swagger-ui-dist/swagger-ui.css';
export default {
props: {
path: {
type: String,
default: '',
},
authenticated: {
type: Boolean,
default: false,
},
authenticator: {
type: Object,
default: () => {},
},
},
watch: {
async authenticated(newValue) {
await this.updateSwagger(newValue);
},
},
async mounted() {
await this.updateSwagger(this.authenticated);
}
methods: {
async updateSwagger(authenticated) {
if (authenticated) {
const token = (await this.authenticator.getToken()).accessToken;
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: '#swagger-ui',
onComplete: function () {
ui.preauthorizeApiKey('token', token);
},
});
} else {
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: '#swagger-ui',
});
}
},
},
};
</script>
It's fine that you're not using it in the template, the issue is that you only check authenticated in the child's mounted hook, which only runs once (and is false at that time).
You should use a watch to track changes to the authenticated prop instead of mounted:
watch: {
authenticated: {
handler(newValue, oldValue) {
this.setUi();
},
immediate: true // Run the watch when `authenticated` is first set, too
}
}
That will call a setUi method every time authenticated changes:
methods: {
async setUi() {
if (this.authenticated) {
let token = (await this.authenticator.getToken()).accessToken;
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: "#swagger-ui",
onComplete: function() {
ui.preauthorizeApiKey("token", token);
}
});
} else {
const ui = swagger.SwaggerUIBundle({
url: this.path,
dom_id: "#swagger-ui"
});
}
}
}
Related
I am setting user authenticity in VUEX Store state if user id and password are valid. But how would I access this in my signIn.vue to redirect?
If user is valid then set VUEX store state i.e isUserAuthentic to true
For this I've used computed properties. to get user state i.e isUserAuthentic and work around in hasUserSignedIn in computed properties. I'm checking if user is authentic then redirect and return undefined from this computed properties. So that I can use this in HTML and doesn't affect HTML because I'm returning undefined from that computed properties.
It is working but that's not perfect/best practices.
SignIn.vue
<template>
<section>
<div class="form-wrapper">
//--------------------------------------USING COMPUTED PROPERTIES
{{ hasUserSignedIn }}
<input v-model="email"/>
<input v-model="password"/>
<button class="btn-signin" #click="submit()">
sign in
</button>
</div>
</section>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
email: '',
password: '',
},
methods: {
submit() {
if (this.email && this.password) {
this.$store.dispatch('signInUser', { email: this.email, password: this.password });
}
},
redirectTohome() {
this.$router.push({ path: '/home' });
},
},
computed: {
...mapGetters(['isUserAuthentic']),
//-----------------------------------------WORKAROUND COMPUTED PROPERTIES
hasUserSignedIn() {
if (this.isUserAuthentic) {
this.redirectTohome();
}
return undefined;
},
},
};
</script>
VUEX signin.js
import Vue from 'vue';
import axios from 'axios';
// const URL = 'http://localhost:3000';
const state = {
signInLoading: false,
isUserAuthentic: false,
};
const getters = {
isSignInLoading: (signInState) => signInState.signInLoading,
isUserAuthentic: (signInState) => signInState.isUserAuthentic,
};
const mutations = {
SET_SIGNIN_LOADING_STATUS(signInState, status) {
signInState.signInLoading = status;
},
SET_USER_AUTHENTICITY(signInState, isAuthentic) {
signInState.isUserAuthentic = isAuthentic;
},
};
const actions = {
async signInUser({ commit }, payload) {
// SET LOADING STATUS TRUE
commit('SET_SIGNIN_LOADING_STATUS', true);
try {
// AUTHORIZE USER WITH AXIOS
const response = await axios.post(`${URL}/api/v1/user/signin`, payload);
// IF USER IS AUTHENTIC, SET AUTHENTIC STATUS TO TRUE
if (response.status === 200) {
commit('SET_USER_AUTHENTICITY', true);
}
} catch (e) {
// SEND TOAST NOTIFICATION TO USER FOR INVALID REQUESTS
if (e.response && e.response.data.message) {
Vue.$toast(e.response.data.message, {
type: 'info',
timeout: 8000,
});
} else {
Vue.$toast('Something went wrong, Please try again.', {
type: 'error',
timeout: 8000,
});
}
}
// SET LOADING STATUS FALSE
commit('SET_SIGNIN_LOADING_STATUS', false);
},
};
export default {
state,
getters,
mutations,
actions,
};
You are pretty much spot on. The only thing I feel you should do is that you can wait for the promise of the store dispatch('signInUser') to resolve and check the state of isUserAuthentic. Something like this:
SignIn.vue
<script>
import { mapGetters } from 'vuex';
export default {
data() {
email: '',
password: '',
},
methods: {
async submit() { // use the 'async' keyword so that you can use await
if (this.email && this.password) {
await this.$store.dispatch('signInUser', { email: this.email, password: this.password }); // wait for the signInUser action to complete
if (this.isUserAuthentic) {
this.redirectTohome();
}
}
},
redirectTohome() {
this.$router.push({ path: '/home' });
}
},
},
computed: {
...mapGetters(['isUserAuthentic']), // mapState will be better here since you don't alter the state
},
};
</script>
<template>
...
{{ isUserAuthentic }}
...
</template>
Also, as an aside, I feel you should fire a commit i.e commit('SET_USER_AUTHENTICITY', false); when there's a failure. That's missing from your code.
Since you are returning the state as is, and not altering it, mapState will be better than mapGetters in your case.
I read the docs and I think WATCHERS are much better than COMPUTED in this particular case.
In this approach we don't have to use {{ hasUserSignedIn }} in HTML. Just watch the property and when it is true redirect to home.
watch: {
isUserAuthentic(val) {
if (val) {
this.redirectTohome();
}
},
},
If someone have better solution, You are more than welcome
So I have the following code in one of my components:
export default {
name: 'section-details',
components: {
Loading
},
mounted() {
if (!this.lists.length || !this.section_types.length) {
this.$store.dispatch('section/fetch_section_form_data', () => {
if (this.section) {
this.populate_form();
}
});
}
else if (this.section) {
this.populate_form();
}
},
computed: {
section_types() {
return this.$store.state.section.section_types;
},
lists() {
return this.$store.state.list.lists;
},
loading() {
console.log(this.$store.state.section.loading);
this.$store.state.section.loading;
}
},
.
.
.
}
As you can see I have a computed property for "loading" that retrieves the attribute from my vuex store for when doing an ajax request.
in my section vuex module i have this:
fetch_section_form_data({ commit }, callback) {
commit("isLoading", true);
sectionService
.fetch_form_data()
.then((data) => {
commit("isLoading", false);
commit("fetch_section_types_success", data.section_types);
commit("list/fetch_lists_success", data.lists, { root: true});
if (callback) {
callback();
}
})
.catch((err) => {
commit("isLoading", false);
})
;
}
then in my mutations for the module i have the following code:
mutations: {
isLoading(state, status) {
state.loading = status;
},
}
Finally in my component where I store the loading property I have this:
<Loading v-if="loading"></Loading>
Anyways, for some reason the Loading component isn't showing up. the console.log in the loading() method however, is returning true for this.$store.state.section.loading. So for some reason Vue isn't picking up that loading == true in the actual DOM. Any help would be appreciated.
You need to return the value from the computed property method:
loading() {
return this.$store.state.section.loading;
}
I'm using vuex to manage the state in my application and doing one way binding with my form.
<script>
import { mapGetters } from 'vuex'
import store from 'vuex-store'
import DataWidget from '../../../../uiComponents/widget'
export default {
data () {
return {
isEdit: false,
msg: {
id: 0,
content: '',
isEnabled: false
}
}
},
components: {
DataWidget
},
computed: mapGetters({
messageId: 'messageId',
messageContent: 'messageContent',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
methods: {
onSave () {
store.dispatch('saveMessage', this.msg, { root: true })
if (this.isMessageValid) {
this.isEdit = !this.isEdit
}
}
},
created () {
this.msg.id = this.messageId
this.msg.content = this.messageContent
this.msg.isEnabled = this.isMessageEnabled
}
}
</script>
<b-form-textarea id="content" v-model="msg.content" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
On load, the values on created() are not binded until I perform an action on the page or refresh the page.
I have tried mounted () hooked same thing.
My Vuex store (Message Module) looks like this:
const state = {
messageId: 0,
messageContent: '',
isMessageEnabled: false,
isMessageValid: true
}
const getters = {
messageId: state => state.messageId,
messageContent: state => state.messageContent,
isMessageEnabled: state => state.isMessageEnabled,
isMessageValid: state => state.isMessageValid
}
const actions = {
getMessage ({commit, rootGetters}) {
api.fetch('api/Preference/Message', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data)
})
}
}
const mutations = {
[types.MESSAGE_LOAD] (state, payload) {
state.messageId = payload ? payload.id : 0
state.messageContent = payload ? payload.content : ''
state.isMessageEnabled = payload ? payload.enabled : false
}
}
export default {
state,
getters,
actions,
mutations
}
and I have a global action (action.js) the gets multiple data:
export const loadSetting = ({ commit, rootGetters }) => {
api.fetchAsync('api/Preference/all', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data.message)
commit(types.HELPDESK_LOAD, data.helpDesk)
commit(types.VOLUME_LOAD, data.volumes)
commit(types.DOWNLOAD_LOAD, data.downloadService)
})
}
My api call:
async fetchAsync (url, token = '') {
let data = await axios.get(HOST + url, {
headers: {
'Authorization': 'bearer ' + token
}
})
return data
}
The problem is your'e calling an async method in Vuex but in the created method, you're treating it like a sync operation and expect to get a value.
You need to use the computed properties you created since they are reactive and will update on every change. In order to make the computed writeable change it to be like this:
computed: {
...mapGetters({
messageId: 'messageId',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
messageContent(){
get () {
return this.$store.getters.messageContent
},
set (value) {
//this is just an example, you can do other things here
this.$store.commit('updateMessage', value)
}
}
}
And change the html to use messageContent:
<b-form-textarea id="content" v-model="messageContent" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
For more info refer to this: https://vuex.vuejs.org/en/forms.html
I am having trouble a variable in vue.js.
Scenario - when a user logs in, I want to set a loggedIn variable to true and set it to false when the user logs out. My code:
index.js:
export default {
state: {
loggedIn : false
},
login() {
var self = this;
var creds = {
username: 'username',
password: 'password'
}
$.ajax({
url: '/login',
type: 'POST',
data: creds,
success: function(response) {
self.state.loggedIn = true;
alert('Logged in!!');
},
error: function() {
alert('Error logging in!');
}
});
},
}
App.vue:
import auth from './index.js';
module.exports = {
data: function () {
return {
loggedIn: auth.state.loggedIn,
}
},
watch: {
loggedIn: function () {
alert('loggedIn value has changed!!!');
}
},
}
As you can see, in App.vue, my loggedIn variable depends on what's imported from index.js. However, it doesn't appear that loggedIn in App.vue is reactive to loggedIn in index.js.
Does anyone know what I might be doing wrong?
Thanks in advance!
In order to make some data reactive, you must set it as the data of a component.
Since auth.state.loggedIn holds a primitive (a Boolean), assigning its value to data.loggedIn simply copies it over to data.loggedIn.
So while data.loggedIn is reactive, auth.state.loggedIn is not. The two are simply never linked up.
The only way to make this work is to assign the whole state object to your data:
module.exports = {
data () {
return {
auth: auth.state,
}
},
watch: {
'auth.loggedIn' () {
alert('loggedIn value has changed!!!');
}
}
};
I have have view router set up:
router.map({
'/tracks/:id': {
component: SingleTrack
}
})
And this is my component (which works with a hard coded URL):
var SingleTrack = Vue.component('track', {
template: '#track-template',
data: function() {
return {
track: ''
}
},
ready: function() {
this.$http.get('//api.trax.dev/tracks/1', function (data) {
this.$set('track', data.track)
})
}
});
How do I pass the url/:id to the end of the $http.get string so i can grab the correct data dynamically when that route in loaded, something like:
ready: function(id) {
this.$http.get('//api.trax.dev/tracks/' + id, function (data) {
this.$set('track', data.track)
})
}
You should be able to get the route parameter from the component $route property :
var itemId = this.$route.params.id;
this.$http.get('//api.trax.dev/tracks/' + itemId, function (data) {
this.$set('track', data.track)
})
See more details in vue.js router documentation
For Best Practises:
index.js(router)
{
path: '/tracks/:id',
name: 'SingleTrack',
component: SingleTrack,
props: (route) => {
const id = Number.parseInt(route.params.id);
return { id }
},
}
SingleTrack.vue
props: {
id: {
type: Number,
required: true,
},
},
mounted(){
this.$http.get('//api.trax.dev/tracks/' +this.id, function (data) {
this.$set('track', data.track)
})
}