swiper icon indicating copy to clipboard operation
swiper copied to clipboard

Swiper incorrectly warns about a wrong number of slides for loop mode

Open MateuszGroth opened this issue 1 year ago • 35 comments

Check that this is really a bug

  • [X] I confirm

Reproduction link

https://codesandbox.io/p/devbox/swiper-react-infinite-loop-vfz433?file=%2Fsrc%2FApp.jsx

Bug description

If you attempt to render a basic loop swiper with any number of slides within jest tests the swiper warns about the incorrect number of slides when it shouldn't

The Swiper

import { Autoplay, Navigation, Pagination } from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/react';

const modules = [Navigation, Pagination, Autoplay];

export const App = () => {
  return (
    <Swiper
      loop
      slidesPerView={1}
      slidesPerGroup={1}
      loopAddBlankSlides
      modules={modules}
    >
      <SwiperSlide>test</SwiperSlide>
      <SwiperSlide>test</SwiperSlide>
      <SwiperSlide>test</SwiperSlide>
      <SwiperSlide>test</SwiperSlide>
      <SwiperSlide>test</SwiperSlide>
      <SwiperSlide>test</SwiperSlide>
    </Swiper>
  );
};

the test

import { render } from '@testing-library/react';

import { App } from './abc';

describe('App', () => {
  it('App', () => {
    render(<App />);
  });
});

[!CAUTION] Swiper Loop Warning: The number of slides is not enough for loop mode, it will be disabled and not function properly. You need to add more slides (or make duplicates) or lower the values of slidesPerView and slidesPerGroup parameters

Expected Behavior

No warning

Actual Behavior

Warning

Swiper version

11.1.4

Platform/Target and Browser Versions

macOS 14.5

Validations

  • [X] Follow our Code of Conduct
  • [X] Read the docs.
  • [X] Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
  • [X] Make sure this is a Swiper issue and not a framework-specific issue

Would you like to open a PR for this bug?

  • [x] I'm willing to open a PR

MateuszGroth avatar Jun 25 '24 10:06 MateuszGroth

Added the bug to the Swiper element as I believe this is where the issue lays

MateuszGroth avatar Jun 25 '24 10:06 MateuszGroth

The 'slides' is an empty list in the loopFix method

MateuszGroth avatar Jun 25 '24 10:06 MateuszGroth

There is this loopCreate method that has initSlides. Shouldn't there be a

swiper.slides = slides

at the end?

like

  const initSlides = () => {
    const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
    slides.forEach((el, index) => {
      el.setAttribute('data-swiper-slide-index', index);
    });
    swiper.slides = slides
  };

MateuszGroth avatar Jun 25 '24 10:06 MateuszGroth

have the same issue

ZhipengZeng avatar Jun 26 '24 14:06 ZhipengZeng

Same issue here

mrleemon avatar Aug 12 '24 12:08 mrleemon

Same issue here

Anakharsis9 avatar Aug 16 '24 07:08 Anakharsis9

As a side effect, this bug causes slideToLoop not to work.

mrleemon avatar Aug 16 '24 08:08 mrleemon

Same issue here, using swiper as a web component in Angular 18.

phber avatar Aug 22 '24 09:08 phber

encounter the same issue.

kiddtang avatar Aug 22 '24 10:08 kiddtang

encounter the same issue for next js

truongqv12 avatar Aug 25 '24 15:08 truongqv12

<section className={"w-full"}>
     <Swiper
       autoplay={{
         delay: 2500,
         disableOnInteraction: false,
         pauseOnMouseEnter: true
       }}
       breakpoints={
         {
           768: {
             slidesPerView: 3,
           }
         }
       }
       centeredSlides={true}
       className={``}
       coverflowEffect={{
         rotate: 0,
         slideShadows: false
       }}
       effect={"fade"}
       grabCursor={true}
       loop={products.length >= 4}
       slidesPerView={1}
       spaceBetween={0}
     >
       {products.map((item, index) => {
         return (
           <SwiperSlide
             key={index}
             className={``}
           >
             <ProductItem product={item} />
           </SwiperSlide>
         );
       })}
     </Swiper>
   </section>
   
my code, this ok

loop={products.length >= 4}

truongqv12 avatar Aug 25 '24 15:08 truongqv12

I’ve had the same issue on a couple of projects now.

thomas-dm avatar Oct 15 '24 09:10 thomas-dm

I'm having this issue as well in vanilla js

mattbloomfield avatar Oct 18 '24 18:10 mattbloomfield

having same issue, any update on this?

Taimoorkhan1122 avatar Oct 19 '24 17:10 Taimoorkhan1122

I made the cyclic slide show work. Swiper version 11.1.14. I used swiper/vue, not swiper/react. But I think you can solve the same thing in React. Two things prevented the work. The first is if the slides are not statically defined, but are added dynamically. The second is that the number of slides must be greater than the slides per view.

In order for Swiper to detect slides, you need to call the update method for the swiper instance after constructing the DOM. I call update for swiper after a second to make sure DOM is built (onMounted). Getting an instance of swiper is also quite a quest. The documentation has a way via useSwiper, but it doesn't work and displays an error. I found a way to get an instance of swiper in the script to call its methods via @swiper. Code example for swiper/vue:

<template>
  <ion-page>
    <ion-content :fullscreen="true">
      <ion-title class="ion-text-center ion-padding-vertical">Популярные события</ion-title>
      <swiper :modules="modules" :slides-per-view="2" :space-between="10" :autoplay="true" navigation
        @swiper="onSwiper" :pagination="{clickable: true}" :loop="true"
      >
        <template v-for="slide in slides" :key="slide.ID">
          <swiper-slide>
            <ion-img :src="'/src/assets/slides/'+slide.BannerImg"></ion-img>
          </swiper-slide>
        </template>
      </swiper>

      <Categories />
    </ion-content>
  </ion-page>
</template>
<script lang="ts">
  import { defineComponent, onMounted, ref } from 'vue';
  import { Pagination, Navigation, Autoplay } from 'swiper/modules';
  import { Swiper, SwiperSlide } from 'swiper/vue';
  import { IonPage, IonTitle, IonContent, IonImg } from '@ionic/vue';

  import 'swiper/css';
  import 'swiper/css/autoplay';
  import 'swiper/css/pagination';
  import '@ionic/vue/css/ionic-swiper.css';

  export default defineComponent({
    components: { IonPage, IonContent, IonTitle, IonImg, Swiper, SwiperSlide },
    setup() {
      const slides = [
        {
            ID: "ID1",
            Title: "Event1",
            BannerImg: "madison.jpg",
        },
        {
            ID: "ID2",
            Title: "Event2",
            BannerImg: "quiz.png",
        },
        {
            ID: "ID3",
            Title: "Event3",
            BannerImg: "madison.jpg",
        },
        {
            ID: "ID4",
            Title: "Event4",
            BannerImg: "quiz.png",
        }];
      const swiper = ref<any>(null);
      const onSwiper = (instance: any) => {
        swiper.value = instance;
      }
      onMounted(() => {
        setTimeout(() => {
          swiper.value.update();
        }, 1);
      });

      return {
        slides, onSwiper,
        modules: [Autoplay, Navigation, Pagination],
      };
    },
  });
</script>

snurbol avatar Oct 21 '24 07:10 snurbol

I made the cyclic slide show work. Swiper version 11.1.14. I used swiper/vue, not swiper/react. But I think you can solve the same thing in React. Two things prevented the work. The first is if the slides are not statically defined, but are added dynamically. The second is that the number of slides must be greater than the slides per view.

In order for Swiper to detect slides, you need to call the update method for the swiper instance after constructing the DOM. I call update for swiper after a second to make sure DOM is built (onMounted). Getting an instance of swiper is also quite a quest. The documentation has a way via useSwiper, but it doesn't work and displays an error. I found a way to get an instance of swiper in the script to call its methods via @swiper. Code example for swiper/vue:

<template>
  <ion-page>
    <ion-content :fullscreen="true">
      <ion-title class="ion-text-center ion-padding-vertical">Популярные события</ion-title>
      <swiper :modules="modules" :slides-per-view="2" :space-between="10" :autoplay="true" navigation
        @swiper="onSwiper" :pagination="{clickable: true}" :loop="true"
      >
        <template v-for="slide in slides" :key="slide.ID">
          <swiper-slide>
            <ion-img :src="'/src/assets/slides/'+slide.BannerImg"></ion-img>
          </swiper-slide>
        </template>
      </swiper>

      <Categories />
    </ion-content>
  </ion-page>
</template>
<script lang="ts">
  import { defineComponent, onMounted, ref } from 'vue';
  import { Pagination, Navigation, Autoplay } from 'swiper/modules';
  import { Swiper, SwiperSlide } from 'swiper/vue';
  import { IonPage, IonTitle, IonContent, IonImg } from '@ionic/vue';

  import 'swiper/css';
  import 'swiper/css/autoplay';
  import 'swiper/css/pagination';
  import '@ionic/vue/css/ionic-swiper.css';

  export default defineComponent({
    components: { IonPage, IonContent, IonTitle, IonImg, Swiper, SwiperSlide },
    setup() {
      const slides = [
        {
            ID: "ID1",
            Title: "Event1",
            BannerImg: "madison.jpg",
        },
        {
            ID: "ID2",
            Title: "Event2",
            BannerImg: "quiz.png",
        },
        {
            ID: "ID3",
            Title: "Event3",
            BannerImg: "madison.jpg",
        },
        {
            ID: "ID4",
            Title: "Event4",
            BannerImg: "quiz.png",
        }];
      const swiper = ref<any>(null);
      const onSwiper = (instance: any) => {
        swiper.value = instance;
      }
      onMounted(() => {
        setTimeout(() => {
          swiper.value.update();
        }, 1);
      });

      return {
        slides, onSwiper,
        modules: [Autoplay, Navigation, Pagination],
      };
    },
  });
</script>

Worked thanks! Here's the React version of it:

import { Swiper, SwiperSlide, SwiperRef } from 'swiper/react';
......
  const swiperRef = useRef<SwiperRef>(null);

  useEffect(() => {
    if (swiperRef.current) {
      setTimeout(() => {
        swiperRef.current?.swiper.autoplay.start();
      }, 1000);
    }
  }, []);



      <Swiper
        ref={swiperRef}
        .....

canercanbaz avatar Nov 12 '24 23:11 canercanbaz

Angular. Who met this bug, this is my solution: constructor() { effect(() => { let users = this.users(); if (users.length) { this.initSwiper(); } }); } I was initing the swiper in ngAfterInit before and got this bug All I needed was to move the code to effect and add a check

PhenomLG avatar Dec 03 '24 19:12 PhenomLG

Because of nature of how the loop mode works (it will rearrange slides), total number of slides must be >= slidesPerView * 2

LuBenv avatar Dec 17 '24 09:12 LuBenv

In my experience this warning is triggered when the check for this warning takes place before the hosting app has rendered the slide elements in the DOM, resulting in functional behaviour, but a false positive on the warning.

Didn't find a clean solution yet, but was able to avoid the warning by using a mutation observer in my UI component and only construct the Swiper instance after all slides were made available in the DOM.

I assume the check for this warning should take place somewhere else during Swiper construction.

peterpolman avatar Dec 17 '24 10:12 peterpolman

Any signs for a fix for this? I've tried using an observer to check all slides were made available in the DOM but that still hasn't fixed it.

MrJonoCES avatar Jan 15 '25 00:01 MrJonoCES

Same issue.

Sharmaryan avatar Jan 15 '25 04:01 Sharmaryan

Apparently, this issue doesn't occur in version 10.x. There must have been a change in how looping works in version 11.x.

mrleemon avatar Jan 18 '25 19:01 mrleemon

Had the same issue - now using swiper element in Vue 3 as swiper/vue is outdated/i just couldn't get it to work correctly.

IT DID NOT WORK IN 11.x. for me without doing it this way.

The key is to use init="false" attribute and then pass the params, loop especially, as html element properties.

<template>
  <swiper-container
    ref="swiperEl"
    v-if="items && items.length > 0"
    :init="false"
  >
    <swiper-slide v-for="(item, idx) in items" :key="idx">
      {{ item.Title }}
    </swiper-slide>
  </swiper-container>
</template>

<script setup>
import { register } from 'swiper/element/bundle';
import { ref, onMounted } from 'vue';

const items = [
  {
    ID: "ID1",
    Title: "Event1",
    BannerImg: "madison.jpg",
  },
  {
    ID: "ID2",
    Title: "Event2",
    BannerImg: "quiz.png",
  },
  {
    ID: "ID3",
    Title: "Event3",
    BannerImg: "madison.jpg",
  },
  {
    ID: "ID4",
    Title: "Event4",
    BannerImg: "quiz.png",
  },
];

// Ref attached to swiper
const swiperEl = ref('swiperEl');

const mods = [EffectFade];
const spaceBetween = 16;

const swiperParams = {
  modules: mods,
  slidesPerView: 1,
  spaceBetween: spaceBetween,
  breakpoints: {
    640: {
      slidesPerView: 2,
    },
    1024: {
      slidesPerView: 3,
    },
  },
  on: {
    init() {
      console.log('Swiper initialized!');
      this.params.loop = true;
    },
  },
};

onMounted(() => {

  // Swiper initialization
  register();

  // Ensure it's ready
  if (!swiperEl.value) {
    return;
  }

  // Assign parameters to swiper element and then init
  Object.assign(swiperEl.value, swiperParams);

  swiperEl.value.initialize();
});
</script>

Warning is now gone, swiper loops and can be swiped correctly.

DOCS HERE

keshiabrown16 avatar Jan 26 '25 00:01 keshiabrown16

Any sign of a fix for this? Downgrading to v10 didn't fix this for me, or using the init="false" attribute

MrJonoCES avatar Jan 29 '25 14:01 MrJonoCES

Well, it took me days to solve this problem... In the end, the solution seems simple, you just need to pay attention to the value of loop and his reactivity The fact is that having Swiper warns makes sense, but after mounted of the component, and depending on the cases and reactivity of loop, the error may or may not be fixed.

Now Swiper will not publish the error and there will be no problem.

I hope this is useful for everyone

  • Swiper Element (WebComponent) with vue
<script lang="ts" setup>
import { PhotoService } from '@/service/PhotoService';

const swiperEl = ref<any>(null);
const images = ref();

const swiperParams = {
  // ....
  navigation: true,
  pagination: true,
  // ....
};

onMounted(() => {
  PhotoService.getImages()
    .then(data => (images.value = data))
    .then(() => {
      Object.assign(swiperEl.value, swiperParams);
      swiperEl.value.initialize();
    });
});
</script>

<template>
  <swiper-container
    ref="swiperEl"
    :init="false" 
    :loop="images?.length > 1"
 >
    <swiper-slide v-for="image in images" :key="image.alt">
      <img
        :src="image.src"
        :alt="image.alt"
      />
    </swiper-slide>
  </swiper-container>
</template>
  • Swiper Vue.js Components
<script setup lang="ts">
import { PhotoService } from '@/service/PhotoService';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Navigation, Pagination } from 'swiper/modules';

const images = ref();

const swiperOptions = computed(() => {
  return {
    // .....
    loop: images.value?.length > 1,
   // .....
   navigation: true,
   pagination: true,
   modules: [Navigation, Pagination],
  };
});

onMounted(() => {
  PhotoService.getImages().then((data) => (images.value = data));
});
</script>

<template>
  <Swiper
    v-bind="swiperOptions"
  >
    <SwiperSlide v-for="image in images" :key="image.alt">
      <img
        :src="image.src"
        :alt="image.alt"
      />
    </SwiperSlide>
  </Swiper>
</template>

ali123plus avatar Feb 14 '25 12:02 ali123plus

If you're using jest with jsdom and haven't explicitly set a width on <Swiper> it will try and pull from element.clientWidth, which is set explicitly to 0 in jsdom. This leads to Swiper not setting swiper.size and downstream will not initialize swiper.slides.

Can check out this issue over on jsdom as to why: https://github.com/jsdom/jsdom/issues/1322

helious avatar Mar 10 '25 21:03 helious

This helped me. loop: true, loopedSlidesLimit: null,

DenisSaliukov avatar Apr 08 '25 12:04 DenisSaliukov

Just want to add my 5 cents: You need to explicitly set width="img.width" height="img.height" inside a slide, otherwise image are not loaded yet during swiper initialization and it thinks that their total width = 0 and gives a warning.

by providing width and height values to DOM element swiper will calculate properly before images are loaded.

SamTyurenkov avatar Apr 23 '25 09:04 SamTyurenkov

We have the same warning in the console with a react integration, but the slides + loop are working.

Tracing swiper/shared/swiper-core.mjs:

loopCreate()
- set const shouldFillGroup using swiper.slides (empty)
- set const shouldFillGrid using swiper.slides (empty)
- if (shouldFillGroup || shouldFillGrid) + params.loopAddBlankSlides 
--> call swiper.recalcSlides() # this will reset swiper.slides
- call swiper.loopFix()

loopFix()
- check swiper.length  (empty)
-- show warning
- call swiper.recalcSlides() # this will reset swiper.slides with the correct number of slides

Other way to calculate the swiper.slides

update() / or init()
- if (param.breakpoint) call swiper.setBreakpoint()

setBreakpoint()
(this check betweens the default swiper value and params value)
 - check params.loop, slidesPerView != params.slidesPerView...
 - if (initialized) and if (needsReLoop) swiper.loopDestroy()
 - or if (initialized) + wasLoop && !hasLoop -> swiper.loopDestroy() 

loopDestroy() 
- call recalcSlides (2 times)

or by calling destroy(), but that's not very useful at this point.

I'm not sure why recalcSlides()/setting swiper.slides is never called before loopCreate.

yanmorinokamca avatar May 27 '25 13:05 yanmorinokamca

I have solved it

There is no other way, force it, roll one, and then roll back

i think i was crazy, lol

const onInit = (swiper: any) => {
  swiper.update()
  swiper.slideToLoop(1, 0)
  setTimeout(() => {
    swiper.slideToLoop(0, 0)
  }, 10)
}

zav1n avatar Aug 22 '25 07:08 zav1n