Hiding Element Based on Route Path or Params in Vue - vue.js

I'm trying to hide the main app navigation bar based on if the route is on a given path.
In my App.vue component, in the created() method. I do check to see if the route is x || y, if either of those are true, I set my Vuex state of show to false. If it is any other route besides those two, I set show = true.
Then in my template I do this
<template>
<div id="app">
<navigation v-show="show"></navigation>
<router-view></router-view>
</div>
</template>
I'm noticing in Vuex tools that my mutations aren't even registering so I'm not sure why that is. Do they need to be actions instead? Here is my full code.
<template>
<div id="app">
<navigation v-show="show"></navigation>
<router-view></router-view>
</div>
</template>
<script>
import Navigation from './components/Navigation/Navigation'
import { firebaseAuth } from './firebase/constants'
import store from './store/index'
export default {
name: 'app',
components: {
Navigation
},
computed: {
show () {
return store.state.navigation.show
}
},
created() {
// Checks for a user and dispatches an action changing isAuthed state to true.
firebaseAuth.onAuthStateChanged(user => {
console.log(store.state.authentication);
console.log(user);
store.dispatch('checkUser', user);
});
// Check if given route is true, if it is then hide Nav.
if (this.$route.path === "/dashboard/products" || this.$route.path === "/dashboard/settings") {
store.commit('hideNav');
} else if (this.$route.path !== "/dashboard/products" || this.$route.path !== "/dashboard/settings") {
store.commit('showNav');
}
}
};
</script>

This may not be working as created is called only once after the instance is created. but when routes changes, it will not be called, so not triggering the mutations you are expecting to trigger on route change, instead of this, you can put a watch on route, so on each route change, you can check whether to show your Nav Bar or not, like following;
Working fiddle: http://jsfiddle.net/ofr8d85p/
watch: {
$route: function() {
// Check if given route is true, if it is then hide Nav.
if (this.$route.path === "/user/foo/posts") {
store.commit('SHOWNAV');
} else {
store.commit('HIDENAV');
}
}
},

Related

Vuex is resetting already set states

Have started to play around with Vuex and am a bit confused.
It triggers the action GET_RECRUITERS everytime I load the component company.vue thus also making an api-call.
For example if I open company.vue => navigate to the user/edit.vue with vue-router and them go back it will call the action/api again (The recruiters are saved in the store accordinly to Vue-dev-tools).
Please correct me if I'm wrong - It should not trigger the action/api and thus resetting the state if I go back to the page again, correct? Or have I missunderstood the intent of Vuex?
company.vue
<template>
<card>
<select>
<option v-for="recruiter in recruiters"
:value="recruiter.id">
{{ recruiter.name }}
</option>
</select>
</card>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
middleware: 'auth',
mounted() {
this.$store.dispatch("company/GET_RECRUITERS")
},
computed: mapGetters({
recruiters: 'company/recruiters'
}),
}
</script>
company.js
import axios from 'axios'
// state
export const state = {
recruiters: [],
}
// getters
export const getters = {
recruiters: state => {
return state.recruiters
}
}
// actions
export const actions = {
GET_RECRUITERS(context) {
axios.get("api/recruiters")
.then((response) => {
console.log('API Action GET_RECRUITERS')
context.commit("GET_RECRUITERS", response.data.data)
})
.catch(() => { console.log("Error........") })
}
}
// mutations
export const mutations = {
GET_RECRUITERS(state, data) {
return state.recruiters = data
}
}
Thanks!
That's expected behavior, because a page component is created/mounted again each time you route back to it unless you cache it. Here are a few design patterns for this:
Load the data in App.vue which only runs once.
Or, check that the data isn't already loaded before making the API call:
// Testing that your `recruiters` getter has no length before loading data
mounted() {
if(!this.recruiters.length) {
this.$store.dispatch("company/GET_RECRUITERS");
}
}
Or, cache the page component so it's not recreated each time you route away and back. Do this by using the <keep-alive> component to wrap the <router-view>:
<keep-alive>
<router-view :key="$route.fullPath"></router-view>
</keep-alive>

how to make the nuxt child component wait until asyncData call get finish

For a form we have 2 components parent(for calling asyncdata and pass data as props to child) & child(form). I can properly fetch the props in child if I navigate using a link. But If I try to refresh the child component page it throws error as no props is passed. Found the reason to be that the parents asyncdata is not completing before the child render to sent the data in props.
Parent Component
<template>
<div>
<p>EDIT</p>
<NewListingModal :is-edit="true" :form-props="this.form" />
</div>
</template>
<script>
import NewListingModal from '#/components/NewListingModal.vue'
export default {
components: { NewListingModal },
async asyncData({ params, store }) {
const listing = await store.$db().model('listings').find(params.listing) //vuexorm call
if (typeof listing !== 'undefined') {
const convertedListing = JSON.parse(JSON.stringify(listing))
return {
name: '',
scrollable: true,
form: {names: convertedListing.names}
}
}
},
}
</script>
child component(other form data is removed to keep it understandable)
<template>
<div v-for="name in this.form.names" :key="name">
<p>{{ name }} <a #click.prevent="deleteName(name)">Delete<a /></a></p>
</div>
</template>
<script>
import Listing from '#/models/listing'
export default {
name: 'ListingModal',
props: {isEdit: {type: Boolean, default: false}, formProps: {type: Object}},
data() {
return {
name: '',
scrollable: true,
form: {names: this.formProps.names}
}
},
methods: {
addName() {
this.form.names.push(this.name)
this.name = ''
},
deleteName(name) {
const names = this.form.names
names.splice(names.indexOf(name), 1)
}
}
}
</script>
How can I make the NewListingModal component rendering wait until the asyncData completes in parent?
In my case, I used asyncData in my parent nuxt component, which fetches the data via store dispatch action, then set it to some store state key, via mutation.
Then I used validate method in my child component. Since Nuxt validate can return promises, I checked the vuex store first for fetched data. If there is none, I refetch it and return the promise instead.
In Parent component.vue
export default {
async asyncData({ params, store }) {
// Api operation which may take sometime
const {data} = await store.dispatch('fetch-my-data')
store.commit('setData', data) //This should be within above dispatch, but here for relevance
}
}
Here I am only fetching and saving to vuex store.
Child component.vue
export default {
async validate({ params, store }) {
let somedata = store.state.data //This is what you've set via parent's component mutation
return !!somedata || store.dispatch('fetch-my-data')
}
}
Here I am returning either the vuex store data (if exists), else refetch it.

Is there any solution for tricking vue's lifecycle hook order of execution?

Destroyed hook is called later than i need.
I tried to use beforeDestroy instead of destroy, mounted hook instead of created. The destroy hook of previous components is always called after the created hook of the components that replaces it.
App.vue
<div id="app">
<component :is="currentComponent"></component>
<button #click="toggleComponent">Toggle component</button>
</div>
</template>
<script>
import A from './components/A.vue';
import B from './components/B.vue';
export default {
components: {
A,
B
},
data: function(){
return {
currentComponent: 'A'
}
},
methods: {
toggleComponent() {
this.currentComponent = this.currentComponent === 'A' ? 'B' : 'A';
}
}
}
</script>
A.vue
<script>
export default {
created: function() {
shortcut.add('Enter', () => {
console.log('Enter pressed from A');
})
},
destroyed: function() {
shortcut.remove('Enter');
}
}
</script>
B.vue
<script>
export default {
created: function() {
shortcut.add('Enter', () => {
console.log('Enter pressed from B');
})
},
destroyed: function() {
shortcut.remove('Enter');
}
}
</script>
Result:
// Click Enter
Enter pressed from A
// now click on toggle component button
// Click Enter again
Enter pressed from A
Expected after the second enter to show me Enter pressed from B.
Please don't show me diagrams with vue's lifecycle, i'm already aware of that, I just need the workaround for this specific case.
Dumb answers like use setTimeout are not accepted.
EDIT: Made some changes to code and description
If you are using vue-router you can use router guards in the component (as well as in the router file) where you have beforeRouteLeave obviously only works where there is a change in route, see here:
https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards

Reload navbar component on every this.$router.push() call

I developing a login/registration system in my Vue.js app. I want the items in navbar to be updated when I call this.$router.push('/').
App.vue:
<template>
<div id="app">
<Navbar></Navbar>
<router-view></router-view>
<Footer></Footer>
</div>
</template>
Navbar component:
export default {
name: "Navbar",
data: function() {
return {
isLoggedIn: false,
currentUser: null
}
},
methods: {
getAuthInfo: function() {
this.isLoggedIn = this.auth.isLoggedIn();
if (this.isLoggedIn) {
this.currentUser = this.auth.currentUser();
}
}
},
mounted: function() {
this.getAuthInfo();
},
updated: function() {
this.getAuthInfo();
}
}
Here is how I redirect to another page:
const self = this;
this.axios
.post('/login', formData)
.then(function(data) {
self.auth.saveToken(data.data.token);
self.$router.push('/');
})
.catch(function(error) {
console.log(error);
self.errorMessage = 'Error!';
});
SUMMARY: The problem is that isLoggedIn and currentUser in Navbar don't get updated when I call self.$router.push('/');. This means that functions mounted and updated don't get called. They are updated only after I manually refresh the page.
I solved the problem with adding :key="$route.fullPath" to Navbar component:
<template>
<div id="app">
<Navbar :key="$route.fullPath"></Navbar>
<router-view></router-view>
<Footer></Footer>
</div>
</template>
Check this out from the docs:
beforeRouteUpdate (to, from, next) {
// called when the route that renders this component has changed,
// but this component is reused in the new route.
// For example, for a route with dynamic params `/foo/:id`, when we
// navigate between `/foo/1` and `/foo/2`, the same `Foo` component instance
// will be reused, and this hook will be called when that happens.
// has access to `this` component instance.
},
I expect your Navbar component is reused across routes so its mounted and updated are not called. Try using beforeRouteUpdate if you want to do some processing on route change.

Navigating vuejs SPA via routes that share component does not refresh component data as expected

I have a couple routes in my vuejs SPA that I have set up using vue-router:
/create/feedback
/edit/feedback/66a0660662674061b84e8ea2fface0e4
The component for each route is the same form with a bit of smarts to change form values based on the absence or present of the ID in the route (feedbackID, in my example).
I notice that when I click from the edit route to the create route, the data in my form does not clear.
Below is the gist of my route file
import FeedbackFormView from './components/FeedbackForm.vue'
// Routes
const routes = [
{
path: '/create/feedback',
component: FeedbackFormView,
name: 'FeedbackCreate',
meta: {
description: 'Create Feedback',
}
},
{
path: '/edit/feedback/:feedbackId',
component: FeedbackFormView,
name: 'FeedbackEdit',
meta: {
description: 'Edit Feedback Form'
},
props: true
}
]
export default routes
Below is the gist of my component
<template lang="html">
<div>
<form>
<input v-model="model.someProperty">
</form>
</div>
</template>
<script>
export default {
data() => ({model: {someProperty:''}}),
props: ['feedbackId'],
created() => {
if (!this.$props['feedbackId']) {
return;
}
// otherwise do ajax call and populate model
// ... details omitted
}
}
</script>
However, if I modify my component as follows, everything works as expected
<template lang="html">
<div>
<form>
<input v-model="model.someProperty">
</form>
</div>
</template>
<script>
export default {
data() => ({model: {someProperty:''}}),
props: ['feedbackId'],
created() => {
if (!this.$props['feedbackId']) {
return;
}
// otherwise do ajax call and populate model
// ... details omitted
},
watch: {
'$route' (to, from) {
if (to.path === '/create/feedback') {
this.model = {}
}
}
}
}
</script>
Why is this? Why do I need watch?
I would have though that changing routes would be sufficient as the purpose of routing is to mimic the semantic behavior of page navigation
You have same component for different routes, when you go to edit route from the create route component is already created and mounted so the state of the component doesn't clear up.
Your component can listen to route changes using $router provided by vue-router every time the route changes the watcher is called.
For those who come this later, the following answer addresses the issue I was facing:
Vue-Router: view returning to login page after page refresh