skip to Main Content

I am trying to build a timer using @pinia/nuxt with nuxt 3. I also want to make an http request when the timer is started to sync my db.

The problem I am facing is that whenever I call the action start, the http request is made for each iteration of the setInterval loop and I only want to run it once.

This is the pinia module. I am calling the start action from an onClick event in a component.

state: () => ({
  timer: {
    id: null,
    isRunning: false,
    time: 5,
    timer: 0,
    state: null,
  } as Timer,
}),
actions: {
  start() {
    this.timer.isRunning = true
    this.syncTimer()
    if (!this.timer.timer) {
      this.timer.timer = setInterval(() => {
        if (this.timer.time > 0) {
          this.timer.time--
        } else {
          clearInterval(this.timer.timer)
          this.reset()
        }
      }, 1000)
    }
  },
  stop() {
    this.timer.isRunning = false
    clearInterval(this.timer.timer)
    this.timer.timer = 0
  },
  reset() {
    this.stop()
    this.timer.time = 1500
  },
  syncTimer() {
    backendAPI<Timer>('/api/timer', {
      method: 'POST',
      body: this.timer
    }).then(({ error, data }) => {
      if (!error.value) {
        const id = data.value?.id ?? ""
        this.timer.id = id
        this.timer.state = "created"
      }
    })
  }
}

My packages.json looks like this:

"devDependencies": {
    "@fortawesome/fontawesome-free": "^6.4.0",
    "@fullcalendar/core": "^6.1.8",
    "@fullcalendar/daygrid": "^6.1.8",
    "@fullcalendar/interaction": "^6.1.8",
    "@fullcalendar/timegrid": "^6.1.8",
    "@fullcalendar/vue3": "^6.1.8",
    "@iconify/vue": "^4.1.1",
    "@kevinmarrec/nuxt-pwa": "^0.17.0",
    "@mdi/font": "^7.2.96",
    "@nuxtjs/google-fonts": "^3.0.0",
    "@pinia-plugin-persistedstate/nuxt": "^1.1.1",
    "nuxt": "3.4.2",
    "sass": "^1.62.0",
    "vuetify": "^3.1.15"
  },
  "dependencies": {
    "@pinia/nuxt": "^0.4.11",
    "pinia": "^2.1.3",
    "vite-plugin-vuetify": "^1.0.2"
  }

2

Answers


  1. As I mentioned in the comments, the correct way of implementing realtime feature, is to use sockets. But, if you need to do it in a polling style, you can write a guard, something like this:

    actions: {
      start() {
        if (this.timer.isRunning) return;
    
        this.timer.isRunning = true;
        this.syncTimer();
    
        this.timer.timer = setInterval(() => {
          if (this.timer.time > 0) {
            this.timer.time--;
          } else {
            clearInterval(this.timer.timer);
            this.reset();
          }
        }, 1000);
      },
      // ...
    }
    

    Hope this helps.

    Login or Signup to reply.
  2. There is no reason why the callback in setInterval would trigger syncTimer() or otherwise repeat the http request, and indeed, when I put you code into a snippet, it doesn’t:

    const { createApp, ref } = Vue;
    const { createPinia, defineStore } = Pinia;
    
    const useTimerStore = defineStore('timerStore', {
      state: () => ({
        timer: {
          id: null,
          isRunning: false,
          time: 5,
          timer: 0,
          state: null,
          isLoading: false,
        },
      }),
      actions: {
        start() {
          this.timer.isRunning = true
          this.syncTimer()
          if (!this.timer.timer) {
            this.timer.timer = setInterval(() => {
              if (this.timer.time > 0) {
                this.timer.time--
              } else {
                clearInterval(this.timer.timer)
                this.reset()
              }
            }, 1000)
          }
        },
        stop() {
          this.timer.isRunning = false
          clearInterval(this.timer.timer)
          this.timer.timer = 0
        },
        reset() {
          this.stop()
          this.timer.time = 5
        },
        async syncTimer() {
          this.timer.isLoading = true
          await new Promise(resolve => setTimeout(resolve, 800))
          this.timer.isLoading = false
          this.timer.id = Math.random() * 100 | 0
          this.timer.state = "created"
        }
      }
    })
    
    
    const App = { 
      data() {
        const timerStore = useTimerStore()
        return {
          timerStore,
        }  
      }
    }
    
    const app = createApp(App)
    const pinia = createPinia()
    app.use(pinia)
    
    app.mount('#app')
    <div id="app">
      <button @click="timerStore.start">Start</button>
      <button @click="timerStore.reset">Reset</button>
    <pre>
    {{ timerStore.timer }}
    </pre>
    (note that id is set only when "start()" is called, not during iterations, and isLoading stays false after the initial load )
    </div>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://unpkg.com/vue-demi"></script>
    <script src="https://unpkg.com/pinia"></script>

    If I am missing something here, feel free to clarify or provide code that reproduces the issue.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search