I'm begginer in vue and i can't resolve my problem with VueRouter.
I got main app component like
<template>
<div>
<Header />
<router-view />
<Footer />
</div>
</template>
One of my Router components has an function to get data from database.
import axios from 'axios';
export default {
name: 'ComponentName',
data() {
return {
dataFromDatabase: []
}
},
methods: {
getData: function() {
// Axios get function to get data from database and add it to this.dataFromDatabase array
}
},
created() {
this.getData();
}
}
Given data are based on url params and it should be changeable when clicking on link that are in header or in other places in a whole app. The problem is that it cannot change if the component won't reload. I know that the problem is that function is called after component is created and is not called again.
So my question is:
Is there any way to watch for url params changes (adding this.$route.params.param to watch() function is not working). Maybe there is a better way to set up my routes or other way to call a function except of created() function or maybe component would reload everytime the link change. As i said links to this can be everywhere even in components that are not setted up in Router
You probably just need watch which by the way is not a function but an object with methods inside
watch: {
'$route'() {
// do something
}
}
you can use a smart watcher that will be watching since the component was created:
watch: {
'$route': {
immediate: true,
handler(newValue, oldValue) {
// ...
}
}
}
Related
I have a simple nuxt.js component like the one below.
Chart is a component that has a method which will receive the data that is fetched in fetch().
If I simply call that method after await fetch('...') I get an error when it's rendered on client-side since the Chart component has not yet been mounted. How could I go about to do something after fetch AND mounted?
And I can't do it in mounted() because then I can't be sure that the fetch is complete.
<template>
<div>
<!--Custom component-->
<Chart ref="chart"/>
</div>
</template>
<script>
export default {
data(){
return {
chartData: []
}
},
async fetch() {
this.chartData = await fetch('https://api.mocki.io/v1/b1e7c87c').then(res =>
res.json()
)
this.$refs.chart.insertSeries(this.chartData) // doesn't work because Chart is not mounted yet.
},
}
</script>
The preferred way of handling this situation would be to use a prop so that <Chart> can handle the data itself, and watch the prop in the child.
Parent
<Chart :chart-data="chartData" />
Chart
export default {
props: ['chartData'],
watch: {
chartData(newValue) {
if(newValue.length) {
this.insertSeries(newValue);
}
}
},
...
}
Variation: You could use v-if instead of a watch:
Parent
<Chart v-if="chartData.length" :chart-data="chartData" />
Chart
export default {
props: ['chartData'],
created() {
this.insertSeries(this.chartData); // `chartData` is guaranteed to exist
}
...
}
Note: There is a slight difference that can emerge between these two options. Imagine you wanted a loading animation while chart data was loading.
With the first option, since the component is shown immediately, the loading functionality would have to be put in the child. In the second option, it would be put in the parent (in a v-else).
In my NuxtJS SSR project with bootstrap-vue as frontend :
I have some page with template and default component
In component there is asyncData(context) function that makes some deal before component render and mounts
Everything is working fine, except only one thing, I need reload (or refresh) a page after some interval automatically and call asyncData again. Standard js window.location.reload() is not good solution, because it`s reloads fully page. I want refresh in vue style, when only changed components re-rendres. I tried to call $nuxt.refresh() but no any effect
Some code example (cut from real project ) , page "index.vue" :
<template>
<div>
<b-tabs content-class="mt-3">
<b-tab title="Test" active><shdashboard searchtype="test"> </shdashboard></b-tab>
</b-tabs>
</div>
</template>
<script>
export default {
name : "index",
async asyncData (context) {
if(process.server)
{
const host = "http://SOMEHOST";
const http = context.$http;
const data = await http.$get(url);
console.log('in server render')
/*
some logic
commit store and prepare data fot b-tab component
*/
}
},
methods : {
refresh() {
console.log("method refresh")
this.$nuxt.refresh(); // Not any effect.
}
},
mounted() {
console.log("page mounted");
setInterval(()=>{
/*
I need to reload page every 15 sec, and call asyncData() to get new values for
<b-tab> component
*/
this.refresh();
},15000);
}
</script>
What do I wrong ?
One way, if I understand your issue correctly, is to use the Vuex store for storing whatever you are fetching in fetch/asyncData. That way you don't need to worry about the internals of Nuxt.js, and what hooks are triggered when.
I.e. something like this:
export default {
computed: {
someData () {
return this.$store.state.someData
},
},
async asyncData ({ store, $axios }){
let data = await $axios.$get(endpoint)
store.commit('setSomeData', data)
},
}
Then just use {{ someData }} in your template!
I'm trying to learn vuex but I think I am missing some basic understanding. Any advice please.
From one component I am dispatching the scale value of my zoomable map to vuex store.
Store.js
export default new Vuex.Store({
state: {
scale:""
},
getters:{
MJERILO: state => {
return state.scale
}
},
mutations:{
STORESCALE: (state, payload) => {
state.scale = payload
}
},
actions:{
RECEIVECURRENTSCALE: (context, payload) => {
context.commit("STORESCALE", payload);
}
}
})
This part is working well because in vue dev tool I can see that the scale number is changing in mutation, state and getters when I do zoom in/out with my mouse. (Do, in mutation is changing automaticaly, and for state and getters I need to press load state. I guess this work like this)
So the problem is probably in the way how I am trying to receive data from vuex state into some other component.
I tried:
Map1.vue
mounted(){
var scale = this.$store.getters.MJERILO
}
But I just get the value stored in state property mjerilo (in this case empty). And I need dynamic that I sent to state.
For static data this worked perfectly (I tried with simple array).
I also tried to retry data in computed, but I have a similar problem. In this case in mounted I get just the first scale value
computed: {
mjerilo(){
return this.$store.getters.MJERILO
}
}
mounted(){
var scale = this.mjerilo
}
I am quite lost. From readings I understand that when ever I scroll my map with mouse I am sending data to action for "registration", than through mutation I am storing this data in state. From state I can get the last updated data (in this case scale) in any other vue component of my app?
UPDATE: I am adding Map1.vue component
<template>
<svg-map name="Zupanije" ></svg-map>
</template>
<script>
import * as d3 from 'd3'
import SvgMap from "./SvgMap"
export default {
name: "Map1",
components: {
"svg-map":SvgMap
},
mounted(){
......lots of javascrip code
.
.
var scale = this.$store.getters.MJERILO
}
}
</script>
I believe you are looking for a function like watch
data: {
scale: this.$state.scale
},
watch: {
'$store.state.scale': (newVal) => {
this.scale = newVal;
}
}
I'm not sure what's wrong but you could try this, what happens then? How you dispatch your action?
<template>
<div>
{{ MJERILO }}
</div>
</template>
import { mapGetters, mapActions } from "vuex";
export default {
...mapGetters(["MJERILO"]),
}
On my main page I have dropdowns that show v-show=show by clicking on the link #click = "show=!show" and I want to set show=false when I change the route. Please advise me on how to realize this thing.
Setup a watcher on the $route in your component like this:
watch:{
$route (to, from){
this.show = false;
}
}
This observes for route changes and when changed ,sets show to false
If you are using v2.2.0 then there is one more option available to detect changes in $routes.
To react to params changes in the same component, you can watch the $route object:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// react to route changes...
}
}
}
Or, use the beforeRouteUpdate guard introduced in 2.2:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
Reference: https://router.vuejs.org/en/essentials/dynamic-matching.html
Just in case anyone is looking for how to do it in Typescript, here is the solution:
#Watch('$route', { immediate: true, deep: true })
onUrlChange(newVal: Route) {
// Some action
}
And yes as mentioned by #Coops below, please do not forget to include :
import { Watch } from 'vue-property-decorator';
Edit:
Alcalyn made a very good point of using Route type instead of using any:
import { Watch } from 'vue-property-decorator';
import { Route } from 'vue-router';
Watcher with the deep option didn't work for me.
Instead, I use updated() lifecycle hook which gets executed everytime the component's data changes.
Just use it like you do with mounted().
mounted() {
/* to be executed when mounted */
},
updated() {
console.log(this.$route)
}
For your reference, visit the documentation.
UPDATE
As stated by #CHANist, router.listen no longer works, I don't know from which version it stopped working, but the good news (as also stated by #CHANist) is we can use:
this.$router.history.listen((newLocation) => {console.log(newLocation);})
OLD Response
The above responses are the better, but just for completeness, when you are in a component you can access the history object inside the VueRouter with:
this.$router.history.
That means we can listen to changes with:
this.$router.listen((newLocation) => {console.log(newLocation);})
I think this is mainly useful when used along with this.$router.currentRoute.path
You can check what I am talking about placing a debugger
instruction in your code and begin playing with the Chrome DevTools Console.
import { useRouter } from "vue-router";
const router = useRouter();
router.afterEach((to, from) => { });
Using Vue3 and the composition API you can do
<script setup lang="ts">
import { watch } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
// do a `console.log(route)` to see route attributes (fullPath, hash, params, path...)
watch(
() => route.fullPath,
async () => {
console.log("route fullPath updated", route.fullPath);
}
);
</script>
References and examples here: https://router.vuejs.org/guide/advanced/composition-api.html#vue-router-and-the-composition-api
Another solution for typescript user:
import Vue from "vue";
import Component from "vue-class-component";
#Component({
beforeRouteLeave(to, from, next) {
// incase if you want to access `this`
// const self = this as any;
next();
}
})
export default class ComponentName extends Vue {}
using Vue Router is an alternative way, use the beforeRouteLeave after methods in your component like this:
<template>
<button #click="ShowMethod">DisplayButton</button>
</template>
<script>
data() {
return { show: true };
},
methods: {
ShowMethod() {
this.show = false;
}
},
beforeRouteLeave(to, from, next) {
this.show = false;
next();
}
</script>
according to VueJs documentation, it's called Navigation Guards check the link below:
Navigation Guards
The leave guard is usually used to prevent the user from accidentally
leaving the route with unsaved edits. The navigation can be canceled
by calling
In-Component Guards:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
beforeRouteLeave(to, from, next) {
// called when the route that renders this component is about to
// be navigated away from.
// has access to `this` component instance.
}
look at the below link for more information:
In-Component Guards
you can use the beforeEach event which allows any function to occur when the route is changing, just don't forget to call the next() function to proceed next operation, basically it has the same job as the backend expressJS middleWare.
router.beforeEach((to, from, next) => {
store.commit('setError', null); //in this example on each route I'm removing the error noted from the old route
document.title = `${to.meta.title} | HartWork`; //on each route I'm adding a prefix to document title.
next(); //calling next to proceed next functions and operations
})
I hope you doing well,
in vue3 and script setup this work is too easy:
watch(route, () => { fetch()})
be careful you must import before
import { watch } from 'vue';
import { useRoute } from 'vue-router';
and define use route :
const route = useRoute()
I have a addToCart component(child) on foodList component(parent). and there is another component Cart. i want to reset the addToCart component's counter value to 0 whenever i will empty my cart.
App.vue
data() {
return {
msg: "Welcome to Your Food Ordering App",
foodData:[],
cart:[],
reset:false
};
},
methods: {
emptyCart:function(){
this.reset = true;
this.cart = [];
}
}
foodList.vue
export default {
props:['foods','reset'],
data() {
return {
};
}
}
<addToCart :reset="reset"></addToCart>
addToCart
export default {
props:['food','reset'],
data(){
return {
counter:0
}
},
beforeMount() {
if(this.reset) {
this.counter = 0;
}
}
in app.vue I'm modifying the reset property to "true" and then passing it to foodList.vue, then passing it to addToCart.vue.
In addToCart.vue I'm checking if reset prop is true then set the counter to 0;
But this is not working.let me know where am I missing?
Please refer to this link for complete code.
food ordering app
So basically you want to pass the state over multiple components. There are multiple ways to achieve this. These are my three recommend ones.
Centralized State management
In order to handle states easier, you can make use of a centralized state management tool like vuex: https://github.com/vuejs/vuex
This is what I recommend you, especially when it comes to bigger applications, where you need to pass the state over multiple levels of components. Trust me, this makes your life a lot easier.
Property binding
The most basic way to communicate with your child components is property binding. But especially when it comes to multi-level communication it can get quite messy.
In this case, you would simply add counter to both of your child components props array like this:
foodList.vue (1. Level Child Component)
export default {
props:['foods','reset', 'counter'],
// ... your stuff
}
And include the component like this:
<foodList :counter="counter"></foodList>
addToCart.vue (2. Level Child Component)
export default {
props:['food','reset', 'counter'],
// ... your stuff
}
And finally include the component like this:
<addToCart :reset="reset" :counter="counter"></addToCart>
As a last step, you can specify counter in the data object of your root component and then modify it on a certain event. The state will be passed down.
App.vue
data() {
return {
// ... your stuff
counter: 0,
};
},
methods: {
emptyCart:function(){
// ... your stuff
this.counter = 0; // reset the counter from your parent component
}
}
Event Bus
As a third option, you could make use of Vue's event bus. This is the option I personally choose for applications, which get too messy with simple property binding, but still are kind of too small to make us of Centralized State management.
To get started create a file called event-bus.js and then add the following code to it:
import Vue from 'vue';
export const EventBus = new Vue();
Now you can simply trigger events from your parent Component like this:
App.vue
import { EventBus } from './event-bus.js'; // check the path
export default {
// ... your stuff
methods: {
emptyCart:function(){
// ... your stuff
EventBus.$emit('counter-changed', 0); // trigger counter-changed event
}
}
}
And then listen to the counter-changed event in your child component.
addToCart.vue
import { EventBus } from './event-bus.js';
export default {
// ... your stuff
created() {
EventBus.$on('counter-changed', newCounter => {
this.counter = newCounter;
});
}
}
Learn more about the event bus: https://alligator.io/vuejs/global-event-bus/