My Nuxt app loads a link and it's child view on the route http://127.0.0.1/user/:id. I put the API calls for the user in mounted hook of the router view.
If route id changes, mounted is not triggered anymore because the child view is already loaded. I ended up with solution - watch the $route.params.id and moved the API call from mounted to this watcher.
watch: {
$route() {
this.getRows()
}
}
Is there a better way to do this?
Solution 1
Force the reload when the route changes defining a :key for the <nuxt-child>, like this:
<nuxt-child :key="$route.fullPath"></nuxt-child>
Solution 2
Put the API call to load the user in a watch to the id coming from the URL instead to mounted, you can use immediate: true to call it in the fist load.
export default {
data() {
return {
user: null
}
},
asyncData({ params }) {
return {
id: params.id
}
},
watch: {
id: {
immediate: true,
handler(id) {
//Call the API to get the user using the id
}
}
}
}
Related
When I dispatch an action in App.vue component in mounted() lifecycle hook, it runs after other components load. I am using async/await in my action and mounted lifecycle hook.
App.vue file
methods: {
...mapActions({
setUsers: "setUsers",
}),
},
async mounted() {
try {
await this.setUsers();
} catch (error) {
if (error) {
console.log(error);
}
}
},
action.js file:
async setUsers(context) {
try {
const response = await axios.get('/get-users');
console.log('setting users');
if (response.data.success) {
context.commit('setUsers', {
data: response.data.data,
});
}
} catch (error) {
if (error) {
throw error;
}
}
},
In Users list component, I need to get users from vuex. So I am using mapGetters to get Users list.
...mapGetters({
getUsers: "getUsers",
}),
mounted() {
console.log(this.getUsers);
},
But the problem is "setting users" console log in running after console logging the this.getUsers.
In Users list component, I can use getUsers in the template but when I try to console log this.getUsers it gives nothing.
How can I run app.vue file before running any other components?
You are using async await correctly in your components. It's important to understand that async await does not hold off the execution of your component, and your component will still render and go through the different lifecycle hooks such as mounted.
What async await does is hold off the execution of the current context, if you're using it inside a function, the code after the await will happen after the promise resolves, and in your case you're using it in the created lifecycle hook, which means that the code inside the mounted lifecycle hook which is a function, will get resolved after the await.
So what you want to do, is to make sure you render a component only when data is received.
Here's how to do it:
If the component is a child component of the parent, you can use v-if, then when the data comes set data to true, like this:
data() {
return {
hasData: false,
}
}
async mounted() {
const users = await fetchUsers()
this.hasData = true;
}
<SomeComponent v-if="hasData" />
If the component is not a child of the parent, you can use a watcher to let you know when the component has rendered. When using watch you can to be careful because it will happen every time a change happens.
A simple rule of thumb is to use watch with variables that don't change often, if the data you're getting is mostly read only you can use the data, if not you can add a property to Vuex such as loadingUsers.
Here's an example of how to do this:
data: {
return {
hasData: false,
}
},
computed: {
isLoading() {
return this.$store.state.app.users;
}
},
watch: {
isLoading(isLoading) {
if (!isLoading) {
this.hasData = true;
}
}
}
<SomeComponent v-if="hasData" />
if you're fetching a data from an API, then it is better to dispatch the action inside of created where the DOM is not yet rendered but you can still use "this" instead of mounted. Here is an example if you're working with Vuex modules:
created() {
this.fetchUsers();
},
methods: {
async fetchUsers() {
await this.$store.dispatch('user/setUsers');
},
},
computed: {
usersGetters() {
// getters here
},
},
Question: Do you expect to run await this.setUsers(); every time when the app is loaded (no matter which page/component is being shown)?
If so, then your App.vue is fine. And in your 'Users list component' it's also fine to use mapGetters to get the values (note it should be in computed). The problem is that you should 'wait' for the setUsers action to complete first, so that your getUsers in the component can have value.
A easy way to fix this is using Conditional Rendering and only renders component when getUsers is defined. Possibly you can add a v-if to your parent component of 'Users list component' and only loads it when v-if="getUsers" is true. Then your mounted logic would also work fine (as the data is already there).
In my vue SPA I have a simple router like this:
{path: '/page/:userId', name: 'user', component: user},
Which means an url like http://xxx.xxx/user/1 shows the component user having the content about user 1.
In case on the same page I have some link as
<vue-link :to="{name:'user', params={userId:3}}">
The router update only the URL but the content page (because it assumes the page is the same where I'm at)
My user content loads data using the url params in data and in watch too
data
userId: this.$route.params.userId || 1,
watch
watch: {
userId: function () {
return this.$route.params.userId || 1;
},
How to fix it without using router-view?
Thanks for any suggestion
If I correctly get your problem which is that you are not able to track your route change. You need to watch the route change, whenever your route changed on a same component it should do something.
watch: {
$route() {
this.updatePage(this.$route.params.userId)
}
},
methods: {
updatePage(param) {
// call api / do something
}
}
I have page '/users'.
export default {
name: 'Users',
created () {
const queryParams = this.$route.query
this[GET_USERS_FROM_SERVER](queryParams)
},
methods: {
...mapActions([GET_USERS_FROM_SERVER]),
onBtnFilterClick () {
this.$route.push('/users?minAge=20&name=alex&withPhoto=true')
}
}
}
When page started, it checks params and gets users from server. But it doesnt work and i think it is because router think that '/users' and '/users?params' is the same path.
If I add this.$router.go() after this.$router.go() it will reload current page and it works. But I want to do it in another way. How can I do this?
Don't reload the page if you do not have to.
this.$route.query can be just as reactive as your other data, so use this fact.
export default {
name: 'Users',
watch: {
'$route.query': {
immediate: true,
deep: true,
handler (queryParams) {
this[GET_USERS_FROM_SERVER](queryParams)
}
}
},
methods: {
...mapActions([GET_USERS_FROM_SERVER]),
onBtnFilterClick () {
this.$route.push('/users?minAge=20&name=alex&withPhoto=true')
}
}
}
When you watch for changes on $route.query, you call this[GET_USERS_FROM_SERVER] whenever it changes. I suspect that this changes the data in your component. I've set the immediate flag to run it when the component is created. I've set the deep flag, because this is an object, and I am not entirely sure if the query object gets replaced with every route change, or just modified. The deep flag will make sure that it will always trigger the handler.
I have a login modal that I activate by setting .is-active to it. For this, I have a method like this:
methods: {
toggleModal: function (event) {
this.isActive = !this.isActive
}
}
that I run onclick. Depending on the boolean value of isActive, my modal gets the class .is-active.
Thing is, in my modal I have a button that takes the user to a new view which means it's rendering a new component, with this code:
<router-link class="control" #click="toggleModal()" to="/register">
As you can see, it's routing to /register. Before doing this, I need to run toggleModal() so that the modal gets closed. Right now it's routing without running the method which means that the new view has my modal overlay which is... not optimal.
Is there any good way to do this in Vue? Could I maybe create a method, that first calls toggleModal(), and then routes from the method?
Thanks.
I would define a method that calls toggleModal first, then navigates. Like so:
methods: {
navigateAway () {
this.isActive = !this.isActive
this.$router.push('/register')
}
}
You don't need the event argument unless you intend on capturing more data from the event or event target. You could also wrap the router push in a setTimeout if you so desire, for perhaps cleaner looking view changes.
methods: {
navigateAway () {
let vm = this
vm.isActive = !vm.isActive
setTimeout(function () {
vm.$router.push('/register')
}, 50)
}
}
Of course, there are hooks that you can use from vue-router that make this easy. Example (assuming you're using single file components and Vue.js 2.x):
export default {
data () {
return {
isActive: false
}
},
beforeRouteLeave (to, from, next) {
this.isActive = false // I assume that you would not want to leave it open upon navigating away
next()
}
}
Link to vue router hooks: https://router.vuejs.org/en/advanced/navigation-guards.html
I use Stamplay as BaaS, so to authenticate user, I just redirect to
/auth/v1/auth0/connect
After, user authenticate.. the Stamplay call my app with
/login/callback?jwt=abc.123.xyz
How can I authenticate user after Stamplay call my app?
Tried
I my router config I try..
'/login/callback': {
component: Vue.extend({
ready() {
// ... THIS IS NOT CALLED!! NEVER
console.log('... ready .. ')
console.log(this.$route.query.jwt)
}
})
}
Try to access this.$route.query.jwt from within the component.
Hi I think you will have a view component for /login/callback in your router config. what you need is when this component is activated, trigger some function right?
So in above view component, where you have data, methods, you can try to do this:
//your component
'/login/callback': {
component: Vue.extend({
data(){
return {
}
},
methods: {},
route: {
activate(){
console.log('... ready .. ')
}
}
}