How to stop Vue.js 3 watch() API triggering on exit

Viewed 1305

I have implemented a watch within a Vue component that displays product information. The watch watches the route object of vue-router for a ProductID param to change. When it changes, I want to go get the product details from the back-end API.

To watch the route, I do this in Product.vue:

import { useRoute } from 'vue-router'

    export default {
      setup() {
        const route = useRoute();

        async function getProduct(ProductID) {
          await axios.get(`/api/product/${ProductID}`).then(..do something here)
        }
    
        // fetch the product information when params change
        watch(() => route.params.ProductID, async (newID, oldID) => {
            await getProduct(newId)
          },
          //watch options
          {
          deep: true,
          immediate: true
          }
        )
      },
    }

The above code works, except that if a user navigates away from Product.vue, for example using the back button to go back to the homepage, the watch is triggered again and tries to make a call to the API using undefined as the ProductID (becaues ProductID param does not exist on the homepage route) e.g. http://localhost:8080/api/product/undefined. This causes an error to be thrown in the app.

  1. Why does the watch trigger when a user has navigated away from Product.vue?
  2. How can this be prevented properly? I can do it using if(newID) { await getProduct(newId) } but it seems counterintuitive to what the watch should be doing anyway.

UPDATE & SOLUTION Place the following at the top replacing the name for whatever your route is called:

if (route.name !== "YourRouteName") {
   return;
}

That will ensure nothing happens if you are not on the route you want to watch.

2 Answers
  1. watch registers the watcher inside an vue-internal, but component-independent object. I think it's a Map. So destroying the component has no effect on the reactivity system.

  2. Just ignore the case where newID is undefined, like you already did. But to prevent wrapping your code in a big if block just use if(newID === undefined)return; at the beginning of your callback. If your ids are always truthy (0 and "" are invalid ids) you can even use if(!newID)return;.

I ran into the same problem. Instead of watching the current route, use vue-router onBeforeRouteUpdate, which only gets called if the route changed and the same component is reused.

From https://next.router.vuejs.org/guide/advanced/composition-api.html#navigation-guards:

import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'

export default {
  setup() {
    // same as beforeRouteLeave option with no access to `this`
    onBeforeRouteLeave((to, from) => {
      const answer = window.confirm(
        'Do you really want to leave? you have unsaved changes!'
      )
      // cancel the navigation and stay on the same page
      if (!answer) return false
    })

    const userData = ref()

    // same as beforeRouteUpdate option with no access to `this`
    onBeforeRouteUpdate(async (to, from) => {
      // only fetch the user if the id changed as maybe only the query or the hash changed
      if (to.params.id !== from.params.id) {
        userData.value = await fetchUser(to.params.id)
      }
    })
  },
}
Related