Swiper: [swiper/react] Custom navigation/pagination components using React refs not working/possible?

Created on 5 Oct 2020  路  14Comments  路  Source: nolimits4web/swiper

  • Swiper Version: 6.2.0
  • Platform/Target and Browser Versions: All

Also posten on Stackoverflow:
Swiper React | How to create custom navigation/pagination components using React refs?

What you did

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

  return (
    <Swiper
      navigation={{
        prevEl: navigationPrevRef.current,
        nextEl: navigationNextRef.current,
      }}
    >
      <SwiperSlide>slide 1</SwiperSlide>
      <SwiperSlide>slide 2</SwiperSlide>
      <div ref={navigationPrevRef} />
      <div ref={navigationNextRef} />
    </Swiper>
  )
}

Expected Behavior

To work

Actual Behavior

Did not work


SwiperJS documentation states that navigation prevEl/nextEl can either be of type "string" or "HTMLElement". Using HTML nodes allows for navigation prevEl/nextEl to be scoped to each rendered instance of MySwiper. In React this is usually done with "refs".

const App = () => (
  <div>
    <MySwiper /> // MySwiper1
    <MySwiper /> // MySwiper2
  </div>
)

In the App example above, navigation prevEl/nextEl from "MySwiper2" should not trigger sliding of "MySwiper1", which is what would happen if one would have used string selectors like { prevEl: '.prev', nextEl: '.next' }. Obviously (if even possible within the application) one could generate unique classnames. A better solution would be to pass the HTML elements as these are already unique. Is this possible somehow with React refs?

My current 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>
  )
}

Thanks in advance!

Most helpful comment

hey guys, 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

All 14 comments

Not possible by passing directly as refs are null initially, so it is possible with your approach or similar:

  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>
  );

@nolimits4web thoughts for Typescript on this one? It doesn't work because I used your example code but nextEl is currently typed so there's an error:

Type 'MutableRefObject<null>' is not assignable to type 'HTMLElement | CSSSelector | undefined'. Type 'MutableRefObject<null>' is missing the following properties from type 'CSSSelector': charAt, charCodeAt, concat, indexOf, and 43 more.

@taze-fullstack you can try this

const navigationPrevRef = useRef<HTMLDivElement>(null);
const navigationNextRef = useRef<HTMLDivElement>(null);

in swiper

        navigation={{
          prevEl: navigationPrevRef.current ? navigationPrevRef.current : undefined,
          nextEl: navigationNextRef.current ? navigationNextRef.current : undefined,
        }}

Could anybody show me full code? (and if you could Typescript?)
I'm strugging making this.

swiper.params.navigation.prevEl = prevRef.current; in onInit can be error:

TS2339: Property 'prevEl' does not exist on type 'boolean | NavigationOptions'.
Property 'prevEl' does not exist on type 'false'.

@rikusen0335 @nolimits4web I'd also appreciate some React/TypeScript help here:

The example below works, but only because of the @ts-ignore due to the type error in the comment above.

const prevRef = useRef<HTMLDivElement>(null)
const nextRef = useRef<HTMLDivElement>(null)

return (
  <Swiper
    navigation={{
      prevEl: prevRef.current ? prevRef.current : undefined,
      nextEl: nextRef.current ? nextRef.current : undefined,
    }}
    onInit={swiper => {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      // eslint-disable-next-line no-param-reassign
      swiper.params.navigation.prevEl = prevRef.current
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      // eslint-disable-next-line no-param-reassign
      swiper.params.navigation.nextEl = nextRef.current
      swiper.navigation.update()
    }}
  >
    <SwiperSlide>1</SwiperSlide
    <SwiperSlide>2</SwiperSlide
    <div ref={prevRef}>Prev</div>
    <div ref={nextRef}>Next</div>
  </Swiper>
)

@michaelthorne Thank you for your code.
I did exact same way because there's only way to cast to any it or, just @ts-ignore.

@rikusen0335 I played around with this a bit more (still in TypeScript) and got the following to work for custom navigation, with refs (so as not to use className):

"swiper": "^6.3.5",
const prevRef = useRef<HTMLDivElement>(null)
const nextRef = useRef<HTMLDivElement>(null)

return (
  <Swiper
     navigation={{
      prevEl: navigationPrev.current!, // Assert non-null
      nextEl: navigationNext.current!, // Assert non-null
    }}
  >
    <SwiperSlide>1</SwiperSlide
    <SwiperSlide>2</SwiperSlide
    <div ref={prevRef}>Prev</div>
    <div ref={nextRef}>Next</div>
  </Swiper>
)

This works for me, without having to call the onInit function and assign the params.

Ohh that's pretty nice code writing. Thank you for your advise, I really appreciate it.

Don't know if this still convenient but. I fixed this issue by import the Navigation module from swiper. and everything works as expected.

import SwiperCore, { Navigation, Pagination, Scrollbar, A11y } from 'swiper';

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


const PaintingsSlider = () => {
    SwiperCore.use([Navigation]);
...

return (
    <Swiper slidesPerView={1}
        loop={true}
        navigation={{
        nextEl: nextRef.current,
        prevEl: prevRef.current,
    }}
....
)

In case someone stumbles here:

@michaelthorne solution works for me, the solution from @ddtch looked very good in the beginning, but the navigation buttons are sometimes not triggering. Dont know why, maybe its the compination with SSR / next.js / next/image (lazy loading images in slides).

@ciruz / @michaelthorne Can you post your whole component? When using the snippet verbatim I get errors that navigationPrev / navigationNext can't be found (because they weren't declared), and if I replace those with prevRef / nextRef respectively, the buttons don't work in runtime. I'm sure I'm just missing something trivial

@danvisintainer sure:

// Foobar.js
import { Swiper, SwiperSlide } from "swiper/react";
import SwiperCore, { Navigation } from "swiper";

import Image from "next/image";
import { useRef } from "react";

SwiperCore.use([Navigation]);

export default function Foobar({ images }) {
  const prevRef = useRef(null);
  const nextRef = useRef(null);

  return (
    <section>
      <h2 className="mb-10">foobar</h2>

      <Swiper
        spaceBetween={16}
        slidesPerView={3}
        loop
        navigation={{
          prevEl: prevRef.current ? prevRef.current : undefined,
          nextEl: nextRef.current ? nextRef.current : undefined,
        }}
        onInit={(swiper) => {
          swiper.params.navigation.prevEl = prevRef.current;
          swiper.params.navigation.nextEl = nextRef.current;
          swiper.navigation.update();
        }}
        breakpoints={{
          320: {
            slidesPerView: 1.5,
          },
          991: {
            slidesPerView: 3,
          },
        }}
      >
        {images.map((image, i) => (
          <SwiperSlide key={`slide_${i}`}>
            <Image
              src={`${process.env.NEXT_PUBLIC_IMAGE_PATH}/${image.path}`}
              width="400"
              height="300"
            />
          </SwiperSlide>
        ))}
        <div className="flex flex-row justify-between mt-5 md:mt-10 md:px-8">
          <div ref={prevRef} className="cursor-pointer">
            <Image
              src="/images/icons/arrow-left-circle-orange.svg"
              height="62"
              width="62"
            />
          </div>
          <div ref={nextRef} className="cursor-pointer">
            <Image
              src="/images/icons/arrow-right-circle-orange.svg"
              height="62"
              width="62"
            />
          </div>
        </div>
      </Swiper>
    </section>
  );
}

hey guys, 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

In case this helps anyone using Swiper with TypeScript:

import SwiperCore from 'swiper';
import {NavigationOptions} from 'swiper/types/components/navigation';
import {PaginationOptions} from 'swiper/types/components/pagination';

const CustomSwiper = () => {
  const navPrevButton = React.useRef<HTMLButtonElement>(null);
  const navNextButton = React.useRef<HTMLButtonElement>(null);
  const paginationLabel = React.useRef<HTMLHeadingElement>(null);

  const onBeforeInit = (Swiper: SwiperCore): void => {
    const navigation = Swiper.params.navigation as NavigationOptions;
    navigation.prevEl = navPrevButton.current;
    navigation.nextEl = navNextButton.current;

    const pagination = Swiper.params.pagination as PaginationOptions;
    pagination.el = paginationLabel.current;
  };

 return (
    <Swiper onBeforeInit={onBeforeInit}>
      <SwiperSlide>1</SwiperSlide>
      <SwiperSlide>2</SwiperSlide>
      <SwiperSlide>3</SwiperSlide>
      <button ref={navPrevButton} />
      <button ref={navNextButton} />
    </Swiper>
  )
}

No need to define navigation/pagination props on Swiper unless you need/want to override other things.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

magic-77 picture magic-77  路  3Comments

Joshanity17 picture Joshanity17  路  3Comments

Uriziel01 picture Uriziel01  路  3Comments

danielcpereira11 picture danielcpereira11  路  4Comments

lxmarinkovic picture lxmarinkovic  路  4Comments