nolimits4web / swiper

Most modern mobile touch slider with hardware accelerated transitions
https://swiperjs.com
MIT License
40.06k stars 9.75k forks source link

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

Open MateuszGroth opened 5 months ago

MateuszGroth commented 5 months ago

Check that this is really a bug

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

Would you like to open a PR for this bug?

MateuszGroth commented 5 months ago

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

MateuszGroth commented 5 months ago

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

MateuszGroth commented 5 months ago

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
  };
ZhipengZeng commented 5 months ago

have the same issue

mrleemon commented 3 months ago

Same issue here

Anakharsis9 commented 3 months ago

Same issue here

mrleemon commented 3 months ago

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

phber commented 3 months ago

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

kiddtang commented 3 months ago

encounter the same issue.

truongqv12 commented 3 months ago

encounter the same issue for next js

truongqv12 commented 3 months ago
<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}

thomas-dm commented 1 month ago

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

mattbloomfield commented 1 month ago

I'm having this issue as well in vanilla js

Taimoorkhan1122 commented 1 month ago

having same issue, any update on this?

snurbol commented 1 month ago

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>
canercanbaz commented 2 weeks ago

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}
        .....