Javascript is required
·
2 min read
·
2858 views

Vue Tip: Re-Rendering Vue Routes When Path Parameters Change

Vue Tip: Re-Rendering Vue Routes When Path Parameters Change Image

In Vue applications, we use Vue Router to define routes and map URLs to components. These components are rendered and updated whenever the URL changes.

A typical use case is a dynamic route pattern with params: /users/:id. It allows you to specify a parameter in the URL. The :id in this route pattern is a placeholder for a dynamic value, which can change based on the user's actions. Typically you will trigger a backend endpoint with the given id to fetch user details.

Let's take a look at an example:

Info

The demo code of this article is interactively available in the following StackBlitz project:

UserDetails.vue
1<script setup>
2import { ref, onMounted } from 'vue'
3import { useRoute, useRouter } from 'vue-router'
4import UserId from '@/components/UserId.vue'
5import { getUserDetails } from '../api/userApi.js'
6
7const route = useRoute()
8const router = useRouter()
9
10const loading = ref(false)
11const details = ref(null)
12
13async function fetchDetails(id) {
14  loading.value = true
15  try {
16    details.value = await getUserDetails(id)
17  } catch (error) {
18    console.error('Failed to fetch details', error)
19    details.value = 'Error'
20  } finally {
21    loading.value = false
22  }
23}
24
25function navigateToNextUser() {
26  const nextId = Number(route.params.id) + 1
27  router.push({ name: 'UserDetails', params: { id: nextId } })
28}
29
30onMounted(async () => {
31  console.log('UserDetails mounted')
32  fetchDetails(route.params.id)
33})
34</script>
35
36<template>
37  <h2>User Details</h2>
38  <div>
39    <span>ID: {{ route.params.id }}</span>
40    <span v-if="loading">Loading details ...</span>
41    <span v-else>Details: {{ details }}</span>
42    <button @click="navigateToNextUser">Next User</button>
43  </div>
44</template>

This component uses the onMounted lifecycle hook to fetch user details when the component is mounted. The user's ID is read via route.params.id from the useRoute composable.

So far, everything works fine and the user details are visible and they get updated if we update the route path manually by changing /users/1 to /users/2.

But our component contains a button that triggers a programmatic route update using router.push.

Warning

Programmatically altering the path does not trigger the re-rendering of the view. As a result, the mounted() hooks do not fire, and the nested components do not reload, leading to unexpected behavior.

Solution 1: Watcher

The simplest solution is to add a watcher that fetches the details if the route params have changed:

UserDetails.vue
1<script setup>
2// ...
3
4watch(
5  () => route.params.id,
6  (newId, oldId) => {
7    fetchDetails(route.params.id)
8  }
9)
10</script>

Solution 2: Route Guard

Another solution is the beforeRouteUpdate route guard:

UserDetails.vue
1<script setup>
2// ...
3
4onBeforeRouteUpdate(async (to, from) => {
5  if (to.params.id !== from.params.id) {
6    fetchDetails(route.params.id)
7  }
8})
9</script>

Info

Of course, you can also use the route guard with Options API.

It adds a navigation guard that triggers whenever the current location is about to be updated. Similar to beforeRouteUpdate but can be used in any component. The guard is removed when the component is unmounted.

Difference Between Solutions 1 & 2

There is a big difference between using watch and beforeRouteUpdate:

The route guard is called before the value of the route object actually changes. The watcher is called after the value of route has changed.

Using the beforeRouteUpdate navigation guard, you can determine whether or not you want to prevent the route from changing or go to a different route entirely.

If you liked this Vue tip, follow me on Twitter to get notified about new tips, blog posts, and more. Alternatively (or additionally), you can subscribe to my weekly Vue & Nuxt newsletter:

I will never share any of your personal data. You can unsubscribe at any time.