Swiper React | How to create custom navigation/pagination components using React refs?
Asked Answered
P

17

42

SwiperJS documentation states that navigation prevEl/nextEl can either be of type "string" or "HTMLElement". Using string selectors is easy enough as:

const MySwiper = (props) => (
  <Swiper
    navigation={{
      prevEl: '.prev',
      nextEl: '.next',
    }}
    {...props}
  >
    <SwiperSlide>slide 1</SwiperSlide>
    <SwiperSlide>slide 2</SwiperSlide>
    <div className="prev" />
    <div className="next" />
  </Swiper>
)

However, how would this be correctly implemented with React refs? Using HTML nodes instead of string selectors allows for navigation prevEl/nextEl to be scoped to each rendered instance of MySwiper.

const App = () => (
  <div>
    <MySwiper className="mySwiper1" />
    <MySwiper className="mySwiper2" />
  </div>
)

In the App example above, navigation prevEl/nextEl from .mySwiper2 should not trigger sliding of .mySwiper1, which is what would happen with string selectors.

My current sad & hacky workaround:

const MySwiper = () => {
  const navigationPrevRef = React.useRef(null)
  const navigationNextRef = React.useRef(null)

  return (
    <Swiper
      navigation={{
        // Both prevEl & nextEl are null at render so this does not work
        prevEl: navigationPrevRef.current,
        nextEl: navigationNextRef.current,
      }}
      onSwiper={(swiper) => {
        // Delay execution for the refs to be defined
        setTimeout(() => {
          // Override prevEl & nextEl now that refs are defined
          swiper.params.navigation.prevEl = navigationPrevRef.current
          swiper.params.navigation.nextEl = navigationNextRef.current

          // Re-init navigation
          swiper.navigation.destroy()
          swiper.navigation.init()
          swiper.navigation.update()
        })
      }}
    >
      <SwiperSlide>slide 1</SwiperSlide>
      <SwiperSlide>slide 2</SwiperSlide>
      <div ref={navigationPrevRef} />
      <div ref={navigationNextRef} />
    </Swiper>
  )
}
Paraselene answered 28/9, 2020 at 9:23 Comment(0)
V
33

While Pierrat's answer did initially solve it for me, I was encountering a bug where the navigation buttons wouldn't do anything until after I'd paused and restarted the Swiper.

To fix, I created my own functions for handling the updates and used those instead.

const MyComponent = () => {
  const sliderRef = useRef(null);

  const handlePrev = useCallback(() => {
    if (!sliderRef.current) return;
    sliderRef.current.swiper.slidePrev();
  }, []);

  const handleNext = useCallback(() => {
    if (!sliderRef.current) return;
    sliderRef.current.swiper.slideNext();
  }, []);

  return (
    <div>
      <Swiper ref={sliderRef}>
        <SwiperSlide />
        ...slides
        <SwiperSlide />
      </Swiper>
      <div className="prev-arrow" onClick={handlePrev} />
      <div className="next-arrow" onClick={handleNext} />
    </div>
  )
}
Vestige answered 22/2, 2022 at 17:46 Comment(1)
i encountered the same bug as you did at pierrat.dev's solution and this solved my problem. thanks.Luana
B
23

just watch out for a little mistake with onBeforeInit into sample of Amine D.

corrected code:

const MySwiper = () => {
  const navigationPrevRef = React.useRef(null)
  const navigationNextRef = React.useRef(null)
  return (
    <Swiper
      navigation={{
        prevEl: navigationPrevRef.current,
        nextEl: navigationNextRef.current,
      }}
     onBeforeInit={(swiper) => {
          swiper.params.navigation.prevEl = navigationPrevRef.current;
          swiper.params.navigation.nextEl = navigationNextRef.current;
     }}
    >
      <SwiperSlide>slide 1</SwiperSlide>
      <SwiperSlide>slide 2</SwiperSlide>
      <div ref={navigationPrevRef} />
      <div ref={navigationNextRef} />
    </Swiper>
  )
}
Berkley answered 18/9, 2021 at 22:11 Comment(2)
"TS2339: Property 'prevEl' does not exist on type 'boolean | NavigationOptions'. Property 'prevEl' does not exist on type 'false'."Evette
To solve @Evette 's TS issue: import {NavigationOptions} from 'swiper/types/modules/navigation' and use a cast: const navigation = swiper.params.navigation as NavigationOptionsBonnybonnyclabber
X
22

Important update: Swiper v8.4.4

Most of the answers to this question refer to the API v6, but a later version (which is at the time of writing this answer is v8.4.4) for example doesn't have a swiper.params.navigation.prevEl instead you should access navigation property directly from the swiper instance like so: swiper.navigation.prevEl.

Here's an updated example using React v18 + Swiper v8.4.4 (not recommended)

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

import 'swiper/css';

const Carousel = () => {
  const navigationNextRef = useRef(null);
  const navigationPrevRef = useRef(null);

  return (
    <div>
      <Swiper
        modules={[Navigation]}
        navigation={{
          prevEl: navigationPrevRef.current,
          nextEl: navigationNextRef.current,
        }}
        onBeforeInit={(swiper) => {
          swiper.navigation.nextEl = navigationNextRef.current;
          swiper.navigation.prevEl = navigationPrevRef.current;
        }}
      >
        <SwiperSlide>
          Slide 1
        </SwiperSlide>

        <SwiperSlide>
          Slide 2
        </SwiperSlide>
      </Swiper>
      <div>
        <button ref={navigationNextRef}>Next</button>
        <button ref={navigationPrevRef}>Prev</button>
      </div>
    </div>
  );
};

Reference the swiper and control it from wherever you want!

Even though the example above works, sometimes the references didn't get the right value, so instead of creating two refs for navigation and/or using a setTimeout() to assign the right values, you can reference the swiper itself and control it using slideNext() and slidePrev(), see James Hooper's answer, you can omit the use of useCallback() in the mentioned answer like so:

import { useRef } from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation } from 'swiper';

import 'swiper/css';

const Carousel = () => {
  const swiperRef = useRef();

  return (
    <div>
      <Swiper
        modules={[Navigation]}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <div>
        <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
        <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
      </div>
    </div>
  );
};

Typescript:

Here's the same example above using Typscript:

/* eslint-disable import/no-unresolved */

// The rule above to shut ESLint from complaining
// about unresolved Swiper's CSS imports
// Why? see: https://github.com/import-js/eslint-plugin-import/issues/2266


import { useRef } from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Swiper as SwiperType, Navigation } from 'swiper';

import 'swiper/css';

const Carousel = () => {
  const swiperRef = useRef<SwiperType>();

  return (
    <div>
      <Swiper
        modules={[Navigation]}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <div>
        <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
        <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
      </div>
    </div>
  );
};

Xavier answered 19/10, 2022 at 16:48 Comment(2)
Thank you for this. I was wondering if there is a way to get the swiper instance at the root component like this. In the docs I can only find the useSwiper() so far.Crandell
@Crandell You can create the ref in the root component and pass it down, or alternatively you can use Forwarding Refs. reactjs.org/docs/forwarding-refs.htmlXavier
O
17

I think I fixed the issue, I also faced the same problem, but finally, let's start

 1. import SwiperCore, { Navigation} from 'swiper'
 2. SwiperCore.use([Navigation])
 3. i will use your exmaple:     

    const MySwiper = () => {
      const navigationPrevRef = React.useRef(null)
      const navigationNextRef = React.useRef(null)
      return (
        <Swiper
          navigation={{
            prevEl: navigationPrevRef.current,
            nextEl: navigationNextRef.current,
          }}
         onBeforeInit={{
              swiper.params.navigation.prevEl = navigationPrevRef.current;
              swiper.params.navigation.nextEl = navigationNextRef.current;
         }}
        >
          <SwiperSlide>slide 1</SwiperSlide>
          <SwiperSlide>slide 2</SwiperSlide>
          <div ref={navigationPrevRef} />
          <div ref={navigationNextRef} />
        </Swiper>
      )
    }

that's it, so if you check Swiper duc there is a page only for API, where you can find a section talking about events that swiper provide, anyway i hope this was helpful

Otten answered 23/2, 2021 at 22:18 Comment(2)
The question is asking about both pagination & navigation!Borghese
thanks for the onBeforeInit , helped me because swiper was showing prev and next slide buttons but click action was not workingWhitmer
P
5

Passing refs directly is apparently not possible in Swiper v6.2.0.

I created a Github issue as well for anyone ending up here where the library author answered. https://github.com/nolimits4web/swiper/issues/3855

Paraselene answered 13/10, 2020 at 8:12 Comment(0)
T
4

As per the previous answers, the following one is the complete code. That may help you to implement as you want.

import React from "react";
import SwiperCore, { Navigation } from 'swiper';
import { Swiper, SwiperSlide } from "swiper/react";

SwiperCore.use([Navigation]);

const MySwiper = () => {
      const navigationPrevRef = React.useRef(null)
      const navigationNextRef = React.useRef(null)
      return (
        <Swiper
          navigation={{
            prevEl: navigationPrevRef.current,
            nextEl: navigationNextRef.current,
          }}
         setTimeout(() => {
          // Override prevEl & nextEl now that refs are defined
          swiper.params.navigation.prevEl = navigationPrevRef.current
          swiper.params.navigation.nextEl = navigationNextRef.current

          // Re-init navigation
          swiper.navigation.destroy()
          swiper.navigation.init()
          swiper.navigation.update()
        })
        >
          <SwiperSlide>slide 1</SwiperSlide>
          <SwiperSlide>slide 2</SwiperSlide>
          <div ref={navigationPrevRef} />
          <div ref={navigationNextRef} />
        </Swiper>
      )
    }

const App = () => (
  <div>
    <MySwiper className="mySwiper1" />
    <MySwiper className="mySwiper2" />
  </div>
)

ReactDOM.render(<App/>, document.getElementById('root'));
Torn answered 14/9, 2021 at 9:25 Comment(0)
S
4

This code is from an answer from the creator of the library and it's what has worked for me.

  const prevRef = useRef(null);
  const nextRef = useRef(null);

  return (
    <Swiper
      onInit={(swiper) => {
        swiper.params.navigation.prevEl = prevRef.current;
        swiper.params.navigation.nextEl = nextRef.current;
        swiper.navigation.init();
        swiper.navigation.update();
      }}
    >
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <div ref={prevRef}>Prev</div>
      <div ref={nextRef}>Next</div>
    </Swiper>
  );
Saleem answered 13/12, 2022 at 10:25 Comment(0)
A
3

2022 Solution source

Here is latest version solution for adding custom pagination or navigation

import { Swiper, SwiperSlide } from 'swiper/react';
import { Pagination } from 'swiper';
import 'swiper/css';
import 'swiper/css/pagination';
import 'swiper/css/navigation';

And:

<Swiper
    modules={[Pagination]}
    pagination={{
        el: '.custom-pagination',
        clickable: true
    }}
    navigation={{
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
    }}
>

And you can put your element anywhere you want with any styles!

<div className="custom-pagination"></div>
Ainu answered 5/12, 2022 at 7:9 Comment(1)
maybe you want to add <div class="swiper-button-prev"></div> <div class="swiper-button-next"></div> elements to your answer. Thanks.Cardiff
O
2

Here is the best solution

 import React, { useRef } from "react";
 // For Typescript 
 // import SwiperCore from "swiper";
 import { Swiper, SwiperSlide } from "swiper/react";
 import "swiper/css";


 const SliderComponent = () => {
 const swiperRef = useRef();

// For Typescript!
// const swiperRef = useRef<SwiperCore>();  


const sliderSettings = {
  440: {
    slidesPerView: 1,
    spaceBetween: 30,
  },
  680: {
    slidesPerView: 2,
    spaceBetween: 30,
  },
  1024: {
    slidesPerView: 3,
    spaceBetween: 30,
  },
};

return (
    <div>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>

      <Swiper
        slidesPerView={3}
        breakpoints={sliderSettings}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>
          Slide 1
        </SwiperSlide>
        <SwiperSlide>
          Slide 2
        </SwiperSlide>
        <SwiperSlide>
          Slide 3
        </SwiperSlide>
        <SwiperSlide>
          Slide 4
        </SwiperSlide>
        <SwiperSlide>
          Slide 5
        </SwiperSlide>
      </Swiper>

      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
    </div>
  );
};

export default SliderComponent;
Orrery answered 7/10, 2022 at 13:41 Comment(0)
O
2

For those who none of these solutions worked for your case (like me): Just use React Refs, but keep in mind that React refs are null on the first render. So you need to trigger a re-render during the swiper initialization, and all will work as expected:

 //Add a state that will trigger a re-render later
 const [_, setInit] = useState(false)

 const prevRef = useRef(null);
  const nextRef = useRef(null);
  return (
    <Swiper
       //assign the refs to the swiper navigation buttons
        navigation={{
          prevEl: prevRef.current,
          nextEl: nextRef.current,
        }}
        //trigger a re-render by updating the state on swiper initialization
        onInit={() => setInit(true)}    >
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <div ref={prevRef}>Prev</div>
      <div ref={nextRef}>Next</div>
    </Swiper>
  );
Overthrow answered 26/4, 2023 at 12:23 Comment(0)
A
2

Just simple and clear way

//import { Swiper, SwiperSlide, useSwiper } from 'swiper/react';

 <Swiper className="mySwiper2">
    <SwiperSlide>...</SwiperSlide>
    ...slides


    <SwiperNavigations />
 </Swiper>

.....

const SwiperNavigations = () => {
  const swiper = useSwiper();

  return (
    <div className="navigation-btns">
      <button onClick={() => swiper.slidePrev()} >PREV</button>
      <button onClick={() => swiper.slideNext()} >NEXT</button>
    </div>
  );
};
Axinomancy answered 8/1 at 5:43 Comment(0)
M
1

Works in this way:

  const prevRef = useRef(null);
  const nextRef = useRef(null);
  return (
    <Swiper
      onInit={(swiper) => {
        swiper.params.navigation.prevEl = prevRef.current;
        swiper.params.navigation.nextEl = nextRef.current;
        swiper.navigation.init();
        swiper.navigation.update();
      }}
    >
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <div ref={prevRef}>Prev</div>
      <div ref={nextRef}>Next</div>
    </Swiper>
  );
Mchale answered 19/9, 2022 at 14:25 Comment(0)
H
0

Use id instead of class

 `navigation={{
     prevEl: "#prev_slide",
     nextEl: "#next_slide",
  }}`
Hildegardhildegarde answered 25/6, 2022 at 6:43 Comment(0)
A
0

For react / ts

navigation={true}
onNavigationNext={(swiper: any) => (navEvent(swiper, "N"))}
onNavigationPrev={(swiper: any) => (navEvent(swiper, "P"))}
Asher answered 20/10, 2022 at 10:43 Comment(0)
B
0

We had a similar issue in Vue where this code wasn't working:

<swiper-container
  navigation-prev-el=".button-prev"
  navigation-next-el=".button-next"
>
  <!-- content -->
</swiper-container>

<button class="button-prev" />
<button class="button-next" />

It wasn't working because when the <swiper-container> is rendered, the buttons still haven't been rendered, because they're later in the DOM.

The solution was simple: move the buttons higher up the DOM so they exist by the time the <swiper-container> is rendered:

<button class="button-prev" />
<button class="button-next" />

<swiper-container
  navigation-prev-el=".button-prev"
  navigation-next-el=".button-next"
>
  <!-- content -->
</swiper-container>
Bonnybonnyclabber answered 4/8, 2023 at 16:4 Comment(0)
F
0

Here is an example of a Swiper slider with external Prev and Next buttons, using React and Swiper Element instead of Swiper React components (as recommended in the docs):

import React, { useRef, useCallback } from "react";
import { register } from "swiper/element/bundle";

register();

function Slider() {
  const swiperRef = useRef(null);

  const handlePrev = useCallback(() => {
    swiperRef.current.swiper.slidePrev();
  }, [swiperRef]);

  const handleNext = useCallback(() => {
    swiperRef.current.swiper.slideNext();
  }, [swiperRef]);

  return (
    <div>
      <div>
        <button onClick={handlePrev}>Previous</button>
      </div>

      <swiper-container ref={swiperRef}>
        <swiper-slide>Slide 1</swiper-slide>
        <swiper-slide>Slide 2</swiper-slide>
      </swiper-container>

      <div>
        <button onClick={handleNext}>Next</button>
      </div>
    </div>
  );
}

export default Slider;
Ftlb answered 5/3 at 13:42 Comment(0)
W
0

2024 Solution for Custom Navigation + Ability to disable on first and last slide.

  const sliderRef: any = useRef(null);
  const prevRef = useRef(null);
  const nextRef = useRef(null);
  const [realIndex, setIndex] = useState(0);
  const [isEnd, setIsEnd] = useState(false);


        <Swiper
          ref={sliderRef}
          onInit={(swiper: any) => {
            swiper.params.navigation.prevEl = prevRef.current;
            swiper.params.navigation.nextEl = nextRef.current;
            swiper.navigation.init();
            swiper.navigation.update();
          }}
          slidesPerView={isXl ? 6 : isLg ? 6 : isMd ? 5 : isSm ? 4 : isXs ? 2 : 2}
          modules={[Navigation]}

          >
             .....
        </Swiper>

         // Previous Button
          <Button
            ref={prevRef}
            disabled={realIndex == 0}
            onClick={() => {
              setIndex(sliderRef.current?.swiper.realIndex);
              setIsEnd(sliderRef.current?.swiper.isEnd)
            }}
          >
           </Button>

      // Next Button
          <Button
            ref={nextRef}
            disabled={isEnd}
            onClick={() => {
              setIndex(sliderRef.current?.swiper.realIndex);
              setIsEnd(sliderRef.current?.swiper.isEnd)
            }}
          >
            {`>`}
          </Button>

Waers answered 14/4 at 22:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.