Home > Blockchain >  Countdown timer doesn't display when a new timeout props pass. How to fix it?
Countdown timer doesn't display when a new timeout props pass. How to fix it?

Time:07-05

I want to make a special component which handles failed fetch requests. It is expected to work in this way:

  • If fetch request fails then several more attempts should be made after several seconds.
  • This special component should display countdown timer for next request to launch.

So I have:

Fetch function is in store. It works fine (makes 3 requests after 3, 6 and 9 seconds).

import { createStore } from "vuex";

const wait = async (ms) => {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
};

export default createStore({
  state: {
    error: {
      isError: false,
      timerSec: null
    }
  },

  mutations: {
    setError(state, payload) {
      state.error = payload.error;
    }
  },

  actions: {
    async fetchProducts({ commit, dispatch }, attempt = 1) {
      try {
        const response = await fetch("https://fakestoreapi.com/products222");

        if (!response.ok) {
          throw new Error("Something went wrong");
        }
      } catch (e) {
        console.log("Request:", attempt);
        commit("setError", {
          error: {
            isError: true,
            timerSec: attempt * 3
          }
        });

        if (attempt >= 3) {
          return;
        }
        await wait(attempt * 3000);
        return dispatch("fetchProducts", attempt   1);
      }
    }
  }
});

I call fetchProducts() in App.vue on mount. In App.vue I pass following data to error-request component:

<template>
  <error-request v-if="error.isError" :timeout="error.timerSec"></error-request>
  <h1 v-else>This should be rendered if there's no errors</h1>
</template>

In the error-request component I have a countDown method which triggered when timeout props changes.

<template>
  <div>
    <h1>The next attempt to fetch data will be made in:</h1>
    <h2>{{ timer }}</h2>
  </div>
</template>

<script>
export default {
  props: ["timeout"],
  data() {
    return {
      timer: null,
      interval: null,
    };
  },

  methods: {
    countDown(sec) {
      this.interval = setInterval(() => {
        this.timer = sec;
        if (sec === 0) {
          clearInterval(this.interval);
          return;
        }
        sec--;
      }, 1000);
    },
  },

  watch: {
    timeout() {
      this.countDown(this.timeout);
    },
  },
};
</script>

Unfortunatelly countdown timer shows only once and only on second request (it ignores first request with countdown from 3 to 1 and ignores third request. Could you help me to fix it?

I made a codesandbox: https://codesandbox.io/s/peaceful-sinoussi-ozjkq8?file=/src/App.vue

CodePudding user response:

You should do:

methods: {
    countDown(sec) {
      this.timer = sec;
      this.interval = setInterval(() => {
        this.timer--;
        if (this.timer === 0) {
          clearInterval(this.interval);
          return;
        }
      }, 1000);
    },
  },

  watch: {
    timeout: {
      handler() {
        this.countDown(this.timeout);
      },
      immediate: true,
    },
  },

There are 2 points to notice:

  • Don't modify the function parameters to prevent side effects (In your case is the sec parameter)
  • You have to trigger the watch for the first time so you need to add the option immediate: true
  • Related