Timeout is not working in Vue 3 when trying to execute a function inside it?

Viewed 74

I'm trying to have a timeout of what is sent to the api in order to control the flow of the calls. When the user clicks a checkbox the timeout is activated in order to wait to see if the user clicks more checkboxes. After that, it sends a commit to the store with the selected checkboxes. However, although when I try it first time it works (the commit is sent only once with the cooldown of the timeout). But when I try to interact with the checkboxes for the second time it calls the commit each time instead of using the timeout.

What I want is that the commit is executed when the timeout is finished.

This is a migration of a component in Vue 2 that worked. The vue 2 code is the following:

<template>
  <PerfectScrollbar>
    <el-menu>
      <p
        v-if="hasSomeSelectedItem"
        class="select-all"
        @click="setAllItems('deselect')"
      >
        {{ _$t('deselect_all') }}
      </p>
      <p v-else class="deselect-all" @click="setAllItems('select')">
        {{ _$t('select_all') }}
      </p>

      <el-menu-item v-for="item in items" :key="item.id">
        <span v-if="type === 'datasets'" class="separator-char">
          <p v-if="item.separatorChar">
            {{ item.separatorChar }}
          </p>
        </span>

        <el-checkbox v-model="item.selected" @change="saveItemsState()">
          {{ item.name }}
        </el-checkbox>
      </el-menu-item>
    </el-menu>
  </PerfectScrollbar>
</template>

<script>
import _clonedeep from 'lodash/cloneDeep'
import { capitalize } from '@/utils/string'
import { PROCESS_ENV } from '@/utils/legacy/process-env'
export default {
  props: {
    options: {
      type: Array,
      default: () => [],
    },
    type: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      items: [],
      timeout: null,
    }
  },
  computed: {
    hasSomeSelectedItem() {
      return this.items.some((item) => item.selected === true)
    },
    getSelectedIds() {
      return this.items.filter((item) => item.selected).map((item) => item.id)
    },
  },
  mounted() {
    this.initializeItems()
    this.saveItemsStateNow()
  },
  methods: {
    initializeItems() {
      const storedItemsIds =
        this.$store.getters['filters/get' + capitalize(this.type) + 'Many']
      const optionsCloned = _clonedeep(this.options)
      if (storedItemsIds === 'all' || storedItemsIds.length === 0) {
        this.items = optionsCloned.map((item) => {
          item.selected = true
          return item
        })
        this.saveItemsState()
      } else {
        this.items = optionsCloned.map((item) => {
          item.selected = storedItemsIds.includes(item.id)
          return item
        })
      }
    },
    saveItemsState() {
      // prevent update & store items for each click on item, wait 1 sec
      clearTimeout(this.timeout)
      this.timeout = setTimeout(() => {
        this.saveItemsStateNow()
      }, PROCESS_ENV.delay.oneSec)
    },
    saveItemsStateNow() {
      this.setItemsStateAll()
      this.setItemsState()
    },
    setItemsState() {
      this.$store.commit(
        'filters/set' + capitalize(this.type) + 'Many',
        this.getSelectedIds
      )
    },
    setItemsStateAll() {
      const isAllSelected = this.getSelectedIds.length === this.items.length
      this.$store.commit(
        'filters/set' + capitalize(this.type) + 'ManyAll',
        isAllSelected
      )
    },
    setAllItems(setter) {
      this.items.forEach((item) => {
        item.selected = setter === 'select'
      })
      this.saveItemsState()
    },
    _$t(label, params = {}) {
      return this.$t(
        `components.project_filters_sidebar.checkbox_menu.${label}`,
        params
      )
    },
  },
}
</script>

While the vue 3 component I'm doing and where the cooldown is not working is the following:

<template>
  <PerfectScrollbar>
    <el-menu>
      <p
        v-if="hasSomeSelectedItem"
        class="select-all"
        @click="setAllItems('deselect')"
      >
        {{ _$t('deselect_all') }}
      </p>
      <p v-else class="deselect-all" @click="setAllItems('select')">
        {{ _$t('select_all') }}
      </p>

      <el-menu-item v-for="item in items" :key="item.id">
        <span v-if="type === 'datasets'" class="separator-char">
          <p v-if="item.separatorChar">
            {{ item.separatorChar }}
          </p>
        </span>

        <el-checkbox v-model="item.selected" @change="saveItemsState()">
          {{ item.name }}
        </el-checkbox>
      </el-menu-item>
    </el-menu>
  </PerfectScrollbar>
</template>

<script lang="ts" setup>
import { ItemsDisplayer } from '@/models/ItemsDisplayer'
import _clonedeep from 'lodash/cloneDeep'
import { capitalize } from '@/utils/capitalize'
import { PropType, ref, onMounted, computed } from 'vue'
import { useStore } from 'vuex'
import { useI18n } from 'vue-i18n'

const store = useStore()
const { t } = useI18n()

const props = defineProps({
  options: {
    type: Array as PropType<ItemsDisplayer[]>,
    default: () => [],
  },
  type: {
    type: String,
    default: '',
  },
})

let items = ref<ItemsDisplayer[]>()
let timeout: ReturnType<typeof setTimeout>

onMounted(() => {
  initializeItems()
  saveItemsStateNow()
})

function initializeItems() {
  const storedItemsIds =
    store.getters['filters/get' + capitalize(props.type) + 'Many']
  const optionsCloned = _clonedeep(props.options)

  if (storedItemsIds === 'all' || storedItemsIds.length === 0) {
    items.value = optionsCloned.map((item) => {
      item.selected = true
      return item
    })
    saveItemsState()
  } else {
    items.value = optionsCloned.map((item) => {
      item.selected = storedItemsIds.includes(item.id)
      return item
    })
  }
}
function saveItemsState() {
  console.log('TIMEOUT ', timeout)
  // prevent update & store items for each click on item, wait 1 sec
  clearTimeout(timeout)
  console.log('TIMEOUT AFTER CLEAR', timeout)
  timeout = setTimeout(() => {
    console.log('DINS TIMEOUT ', timeout)
    saveItemsStateNow()
  }, 3000)
}
const hasSomeSelectedItem = computed(() => {
  return items.value?.some((item) => item.selected === true)
})

const getSelectedIds = computed(() => {
  return items.value?.filter((item) => item.selected).map((item) => item.id)
})

function saveItemsStateNow() {
  console.log('saveItemsStateNow')
  setItemsStateAll()
  setItemsState()
}

function setItemsState() {
  store.commit('filters/set' + capitalize(props.type) + 'Many', getSelectedIds)
}

function setItemsStateAll() {
  const isAllSelected = getSelectedIds.value?.length === items.value?.length
  store.commit(
    'filters/set' + capitalize(props.type) + 'ManyAll',
    isAllSelected
  )
}

function setAllItems(setter: string) {
  items.value?.forEach((item) => {
    item.selected = setter === 'select'
  })
  saveItemsState()
}

function _$t(label: string, params = {}) {
  return t(`components.project_filters_sidebar.checkbox_menu.${label}`, params)
}
</script>

I think that or the timeout is not working or it seems to have the function that commits to the store twice (it seems like it still commits even with the timeout going on) Thank you!!

0 Answers
Related