data key value changed in dialog route not being seen by other routes
-
I have a button icon on the toolbar which brings up a dialog (by route). When the user enters a pin correctly a “adminMode” key on the vue data is set to true and then it’s routed back to the “home” subroute. In the toolbar there is a drawer menu that is only visable when this mode is set and yet it doesn’t appear. If I set the mode in the code for the home route it does show up so must me my misunderstanding about how vue data keys are updated between components/routes
Could be just my ignorance of vue in general or it might be something to do with pushing a subroute not refreshing the toobar ofthe parent (index) router.ng this repo
I am modifying this code https://github.com/claustres/quasar-feathers-tutorialy
by @luc-claustresdialog route to Admin.vue
<template> </template> <script> import { Toast, Dialog } from 'quasar' // import api from 'src/api' export default { data () { return { } }, computed: { }, methods: { login (pin) { // return api.authenticate({ // strategy: 'local', // password: pin }) if (pin === '1234') { return Promise.resolve() } else { return Promise.reject() } } }, mounted () { Dialog.create({ title: 'Enter Administrator Mode', form: { pin: { type: 'password', label: 'Pin', model: '' } }, buttons: [ { label: 'Ok', handler: (data) => { this.login(data.pin) .then(_ => { this.$data.adminMode = true Toast.create.positive('You are now in admin mode') this.$router.push({ name: 'home' }) }) .catch(_ => { Toast.create.negative('Incorrect Pin - Try Again') this.$router.push({ name: 'home' }) }) } } ] }) }, beforeDestroy () { } } </script> <style lang="styl"> </style>
Index.vue
<template> <q-layout> <div slot="header" class="toolbar"> <button @click="$refs.menu.open()" v-show="admin_mode"> <i>menu</i> <q-tooltip anchor="bottom middle" self="top middle" :offset="[0, 20]">Menu</q-tooltip> </button> <q-toolbar-title :padding="0"> Home Lighting </q-toolbar-title> <button class="primary circular" @click="goTo('admin')" v-show="admin&&!admin_mode"> <i>settings</i> <q-tooltip anchor="bottom middle" self="top middle" :offset="[0, 20]">Enter Admin Mode</q-tooltip> </button> <button class="primary circular" @click="admin_mode_off()" v-show="admin_mode"> <i>stop</i> <q-tooltip anchor="bottom middle" self="top middle" :offset="[0, 20]">Exit Admin Mode</q-tooltip> </button> <!--q-tabs slot="navigation"> <q-tab route="/singin" exact replace>Sign In</q-tab> <q-tab route="/singup" exact replace>Register</q-tab> <q-tab icon="featured_play_list" route="/chat" exact replace>Your Tasks</q-tab> </q-tabs--> </div> <!-- system settings menu admin only --> <q-drawer swipe-only left-side ref="menu" > <div class="toolbar light"> <i>menu</i> <q-toolbar-title :padding="1"> Menu </q-toolbar-title> </div> <q-drawer-link icon="home" to="/chat">Home</q-drawer-link> <q-drawer-link icon="chat" to="/chat">Chat</q-drawer-link> <q-collapsible icon="info" label="About"> <p style="padding: 25px;" class="text-grey-7"> This is a template project combining the power of Quasar and Feathers to create real-time web apps. </p> </q-collapsible> </q-drawer> <!-- sub-routes --> <router-view class="layout-view" :user="user"></router-view> </q-layout> </template> <script> import { Toast } from 'quasar' import api from 'src/api' import users from 'src/users' export default { data () { return { user: null } }, computed: { authenticated () { return users.authenticated() }, admin () { return users.admin() }, admin_mode () { return this.$data.adminMode } }, methods: { goTo (route) { this.$router.push({ name: route }) }, admin_mode_off () { this.$data.adminMode = false }, getUser (accessToken) { return api.passport.verifyJWT(accessToken) .then(payload => { return api.service('users').get(payload.userId) }) .then(user => { this.$data.user = user return user }) } }, mounted () { // Check if there is already a session running api.authenticate() .then((response) => { return this.getUser(response.accessToken) }) .then(user => { Toast.create.positive('Restoring previous session') }) .catch(_ => { this.$router.push({ name: 'home' }) }) // On successfull login api.on('authenticated', response => { this.getUser(response.accessToken) .then(user => { this.$router.push({ name: 'home' }) }) }) // On logout api.on('logout', () => { this.$data.user = null this.$router.push({ name: 'home' }) }) }, beforeDestroy () { } } </script> <style lang="styl"> </style>
hHomerouter.vue sub
<template> <div class="layout-padding"> <div class="column items-center"> <div v-if="authenticated"> <div v-if="admin"> <p> admin user </p> <div v-if="admin_mode"> <p> in admin mode </p> </div> </div> <div v-else > <p> authenticated user </p> </div> </div> <div v-else > <p> See system administrator for how authenticate on system </p> </div> </div> </div> </template> <script> import users from 'src/users' export default { props: ['user'], data () { return { } }, computed: { authenticated () { return users.authenticated() }, admin () { return users.admin() }, admin_mode () { return users.admin_mode() } }, methods: { }, mounted () { }, beforeDestroy () { } } </script> <style lang="styl"> </style>
-
this.$data.adminMode = true
Most probably, this is the statement that does not work.
You should probably read these two paragraphs:
https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties
What this basically means (and before you go and use $set, which should be a last resort), is that you should declare all of the values that need reactivity in your “data” section, just set dummy/empty values even if you know they’ll be overwritten by some function immediately. Not declaring them makes it all NOT work as you would expect.
So just try and do this:<template> </template> <script> import { Toast, Dialog } from 'quasar' // import api from 'src/api' export default { data () { return { adminMode: false } }, compu...
and report back
-
From the original code I had seen that data keys were being “preset/declared” like for user. I tried what you suggested before and that did not work. I also tried putting that in the Index.vue data export and the Home.vue data export sections thinking it might need to be “preset/declared” before the dialog is fired. All those didn’t work. I should have said I tried this already, but this is why I am asking as that didn’t work.
-
per the vue2 page referenced i tried putting the “declaration” in main.js like this.
Quasar.start(() => { /* eslint-disable no-new */ new Vue({ data: { adminMode: false }, router, el: '#q-app', render: h => h(require('./App')) }) })
but still no love
-
even tried $set as last resort and still no “reactivity”. I’m out of ideas.
label: 'Ok', handler: (data) => { this.login(data.pin) .then(_ => { // this.$data.adminMode = true this.$set(this.$data, 'adminMode', true) Toast.create.positive('You are now in admin mode') this.$router.push({ name: 'home' }) }) .catch(_ => { Toast.create.negative('Incorrect Pin - Try Again') this.$router.push({ name: 'home' }) })
-
A bit of investigation with the console and it seems that my computed function in Index.vue is not called when the value of adminMode is changed in the Admin.vue dialog. If it is never called the the v-show in the template never calls it (except when it is first rendered).
admin_mode () { return this.$data.adminMode }
<button class="primary circular" @click="admin_mode_off()" v-show="admin_mode"> <i>stop</i> <q-tooltip anchor="bottom middle" self="top middle" :offset="[0, 20]">Exit Admin Mode</q-tooltip> </button>
I’m to much a noob to express this but it seems that change in the DOM data is not triggering the re-rendering of the Index.vue template.
So still scratching my head on how to get this to happen -
Maybe there’s something wrong with my reasoning, in this case I truly hope someone with better Vue expertise will correct me.
In my mindcomputed
properties are for stuff that is expensive to calculate, and you don’t want it recalculated each time it’s used.
So the computed value is recalculated only when some other value V that it depends on has changed.
I assume that V must also be a piece of reactive Vue data for this to happen.
You should not need to use $data nor $set.I hope this gets you further along.
It seems your search for a solution has got you a bit confused. I would start from scratch, right after reading again a couple of Vue docs regarding computed and reactivity, and keeping these last comments in mind.Or maybe computed is simply not the right tool for the job, and maybe a method would work best for you.
-
It seems I remembered correctly
from https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
[…] computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed. This means as long as message has not changed, multiple access to the reversedMessage computed property will immediately return the previously computed result without having to run the function again. -
@spectrolite thx
After further study it seems that the issue is vue-router and the parent/child subroutes and the <vrouter-view> tag. Passing state between is not as trivial as when one embeds a child component directly in a parent template. If I embed my child component (Admin.vue) directly in Index.Vue it works (reactivity works).
So passing state between routed children I guess is not so trivial. I’ve decided to try vuex as it has some built in ways to pass state to routed child components.
Still if someone has a best practices way to modify(mutate) the state in a child (called by a vue-router) that will then re-render in the parent please share
-
I still haven’t figured out two way data between parent and child routes but I decided to have the admin dialog be a method in parent instead of a child route and that solves my particular issue of state. So we’ll call this solved.