Home > database >  VueJS 3: update value and avoid watch
VueJS 3: update value and avoid watch

Time:09-11

I am coding a table with pagination component, and I use multiple v-model and watch on these variables to fetch the data. When perPage is updated, I want to reset page to 1. So I did it in my watch method but of course, watch is triggered twice (for perPage and then page).

Is it possible to update the variable and disable watch at this moment ?

Here is my current code:


<script setup lang="ts">

const sort = ref(route.query.sort || 'created_at')
const filters = ref(route.query.filters || {})
const page = ref(route.query.page ? parseInt(route.query.page.toString()) : 1)
const perPage = ref(route.query.per_page ? parseInt(route.query.per_page.toString()) : 10)

watch([sort, filters, page, perPage], ([oldSort, oldFilters, oldPage, oldPerPage], [newSort, newFilters, newPage, newPerPage]) => {
  if (oldPerPage !== newPerPage)
    page.value = 1

  fetchItems()

  router.push({
    query: {
      ...route.query,
      sort: sort.value,
      // filters: filters.value,
      page: page.value,
      per_page: perPage.value,
    },
  })
})

async function fetchItems() {
  items.value = await userApi.list({
    filters: toRaw(filters.value),
    sort: sort.value,
    page: page.value,
    perPage: perPage.value,
  })
}
</script>

<template>
    <CTable
      :pagination-enabled="true"
      v-model:sort="sort"
      v-model:page="page"
      v-model:per-page="perPage"
      :total-items="items.meta.total"
      :total-pages="items.meta.last_page"
    />
</template>

The only workaround I found is to return when I reset page:

watch(..., () => {
  if (oldPerPage !== newPerPage) {
    page.value = 1
    return
  }

  fetchItems()

  ...
})

It is working in my case but for some another cases I would like to update without trigger the watch method.

Thanks!

CodePudding user response:

Create another watcher for perPage :

watch([sort, filters, page, perPage], ([oldSort, oldFilters, oldPage, oldPerPage], [newSort, newFilters, newPage, newPerPage]) => {

  fetchItems()

  router.push({
    query: {
      ...route.query,
      sort: sort.value,
      // filters: filters.value,
      page: page.value,
      per_page: perPage.value,
    },
  })
})

watch(perPage, (newPerPage,oldPerPage ) => {
  if (oldPerPage !== newPerPage)
    page.value = 1
})

It's recommended to create watch for a single property separately to avoid unnecessarily updates and conflicts. For the first watch try to replace it with watchEffect like since you're not using the old/new value:

watchEffect(() => {
  fetchItems()
  router.push({
    query: {
      ...route.query,
      sort: sort.value,
      // filters: filters.value,
      page: page.value,
      per_page: perPage.value,
    },
  })
})

CodePudding user response:

Considering that the state is changed through v-model, it needs to be observed with a watcher.

In order to avoid a watcher to be triggered multiple times, it should implement additional conditions like shown in the question, but it also needs to not skip an update when page is already 1, this won't result in additional update:

  if (oldPerPage !== newPerPage) {
    page.value = 1

    if (newPage !== 1)
      return
  }

Otherwise there need to be multiple watchers, like another answer suggests. In case a watcher that causes asynchronous side effects (fetchItems) shouldn't be triggered more than it needs, and there are other causes for this to happen besides perPage, it can be debounced, e.g.:

watch(perPage, () => {
  page.value = 1
});

watch([sort, filters, page, perPage], debounce(() => {
  fetchItems()
  ...
}, 100));
  • Related