Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mikromike's avatar

Vue3, Composite Api, script setup. loop(setTimeout) does not work than one index forward.

Hello

Long time when I have done something with Vue.

two components: main as BannerComponent and child as AdSlide.

  1. main problem is this line is running only once:

     		  interval.value = setTimeout(changeBanner, 3000);
    

so it changes index from 0 to 1 and stops.

if I add startBanner() after changeBanner has changed currentBanner.value += 1; it creates endless index loop.

  <template>            
     <div  v-if="banners">         
      <div v-for="banner, index in banners.banners"  v-bind:key="banner.id" :iscurrent="currentBanner ===   
         index">    
      <div v-show="currentBanner === index">           
           <ad-slide  :banner="banner" :index="key"></ad-slide>               
     </div>            
   </div>    
  </div> 
 </template>


  <script setup>
       import axios from "axios";
      import { ref, onMounted, onUpdated, reactive } from "vue";
     import AdSlide from "./AdSlide.vue";

     const iscurrent = ref(0);
    const currentBanner = ref(0);
    const timeElapsed = ref(0);
   const interval = ref(null);
   let bannerData = window.location;  
   let banners = ref([]);
  let errors = ref(''); 
  let getBannersCount = ref(null);


   const startBanner = () => {
        clearTimeout(interval.value);
       interval.value = setTimeout(changeBanner, 3000);
     };

    const changeBanner = () => {
     if(currentBanner.value === getBannersCount.value -1){ 
       currentBanner.value = 0;
        } else {
           currentBanner.value += 1;  
       }   
   };

     async function getBanners()  {
              console.log(bannerData);
      try {
        const response =  await axios.post("http://project.test:8000/mainos", bannerData);
       banners.value = response.data;
         getBannersCount.value = response.data.length;       <-- 

           startBanner();
           } catch(error)  {
                console.error("Error fetching banners:", error);
        }
     }


     onMounted(() => {
            console.log('BannerComponent mounted');
          getBanners();   
      })  

here is my child component:

 <template>
     <div id="" class="flex justify-center banner" >    
   <img class="object-cover" :src="`${url}`" alt= "`${alt}`">     
</div>
  • i think, this is part of problem:

    getBannersCount.value = response.data.length;

I am using as backend Laravel 10 and here is controller part:

					  $banners =  $site->banners()->get();
					return response()->json([     
            						'banners' => $banners                
    				 ]);

Thanks Mika.

0 likes
9 replies
LaryAI's avatar
Level 58

It seems like the issue you're encountering is related to the setTimeout function not being called repeatedly to cycle through the banners. The changeBanner function is only called once because startBanner is not being called again after the timeout function executes.

To create a continuous loop, you need to ensure that startBanner is called again at the end of changeBanner. Here's how you can modify your changeBanner function to achieve this:

const changeBanner = () => {
  if (currentBanner.value === getBannersCount.value - 1) { 
    currentBanner.value = 0;
  } else {
    currentBanner.value += 1;  
  }
  startBanner(); // Call startBanner again to set the next timeout
};

Additionally, you should clear the existing timeout in startBanner to prevent multiple timeouts from being set if startBanner is called multiple times:

const startBanner = () => {
  clearTimeout(interval.value); // Clear any existing timeout
  interval.value = setTimeout(changeBanner, 3000);
};

Make sure that startBanner is only called once after the banners are successfully fetched. It should not be called again in changeBanner, as the setTimeout inside startBanner will handle the loop.

Here's the updated startBanner and changeBanner functions:

const startBanner = () => {
  clearTimeout(interval.value);
  interval.value = setTimeout(changeBanner, 3000);
};

const changeBanner = () => {
  if (currentBanner.value === getBannersCount.value - 1) { 
    currentBanner.value = 0;
  } else {
    currentBanner.value += 1;  
  }
  startBanner(); // Restart the banner loop
};

With these changes, your banner should cycle through continuously every 3 seconds. Make sure to clear the timeout when the component is unmounted to prevent memory leaks:

import { onUnmounted } from 'vue';

onUnmounted(() => {
  clearTimeout(interval.value);
});

This ensures that the timeout is cleared and no further changes to currentBanner are made once the component is no longer in use.

mikromike's avatar

and Vue sees in Console.log : Object { banners: (4) […] } , Inside array, there is four objects.

mikromike's avatar

Sorry AI Larry: same result endless loop for index.

mikromike's avatar
mikromike
OP
Best Answer
Level 29

Sorry Larry, you cannot beat human ,-D solution was:

    banners.value = response.data;
  getBannersCount.value = response.data.banners.length;   
gych's avatar

I still found some issues with your current code. You're using a timeout which will not result in a rotation of the banners because the timeout is only called once. I've changed it to an Interval together with some other improvements to simplify your code.

Here is an improved version

<template>             
  <div v-if="banners" v-for="(banner, index) in banners" :key="banner.id" v-show="currentBanner === index">             
    <ad-slide :banner="banner" :index="index" />                    
  </div> 
</template>

<script setup>
  import axios from "axios";
  import { ref, onMounted, onBeforeUnmount } from "vue";
  import AdSlide from "./AdSlide.vue";

  const currentBanner = ref(0);
  const bannerData = window.location;  
  const banners = ref(null);
  const interval = ref(null);

  const changeBanner = () => currentBanner.value = (currentBanner.value + 1) % banners.value.length;
 
  async function getBanners()  {
    console.log(bannerData);
    try {
      const response = await axios.post("http://project.test:8000/mainos", bannerData);
      banners.value = response.data.banners;
      interval.value = setInterval(changeBanner, 3000);
    } catch(error)  {
      console.error("Error fetching banners:", error);
    }
  }

  onMounted(() => {
    console.log('BannerComponent mounted');
    getBanners();   
  })  

onBeforeUnmount(() => {
	if(interval.value) clearInterval(interval.value)
});
</script>
mikromike's avatar

My current code works. but I have problem to implement to live server. "Cross-Origin Request Blocked: The Same Origin Policy disallows".

these two files are complies js and css. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.

in dev site npm run preview shows banner, not live server.

config/cors.php

'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
 ' supports_credentials' => false,
 ];

index.php

 /* temp CORS error fix */

 header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers:*");
header("X-Content-Type-Options: nosniff");
gych's avatar

@mikromike When I tested your code the banner rotation didn't work because startBanner is only called once. Its also not a good practice to use a timeout and use it as an interval. Then you should just use an interval instead but its not only about the interval also look at the other improvements I made.

About the CORS issue I guess your Vue SPA is separated from your Laravel back-end and you're using Laravel as API ?

mikromike's avatar

@gych, no clients( vue3 app) are from separate domain around 300 pcs. backend is laravel. Banner are works nicely: http://frankvps.server.datapilvi.fi/ -- I have fixed CORS issue with:

     $siteSource = $request->origin;    
     $siteName = Str::remove('http://',  $siteSource);          
     $siteName = Str::remove('.fi',  $siteName);  

all domains are listed in db and checks, with domain and port part, as vue3 client are servers..

mikromike's avatar

console.warnings:

 Module source URI is not allowed in this document: http:://domain.com/assets/index-CAeSnsUJ.js”.

if put url to browser, file is there.

Please or to participate in this conversation.