34

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>
  )
}
maeertin
  • 421
  • 1
  • 4
  • 6

14 Answers14

23

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>
  )
}
James Hooper
  • 1,475
  • 13
  • 24
21

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>
  )
}
pierrat.dev
  • 211
  • 2
  • 2
  • 1
    "TS2339: Property 'prevEl' does not exist on type 'boolean | NavigationOptions'.   Property 'prevEl' does not exist on type 'false'." – cordial Dec 21 '22 at 14:34
  • To solve @cordial 's TS issue: `import {NavigationOptions} from 'swiper/types/modules/navigation'` and use a cast: `const navigation = swiper.params.navigation as NavigationOptions` – Alec Aug 04 '23 at 10:28
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

Amine D
  • 171
  • 1
  • 4
14

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

Yassine Sedrani
  • 619
  • 5
  • 12
  • 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. – Guinevere Dec 28 '22 at 08:14
  • @Guinevere You can create the ref in the root component and pass it down, or alternatively you can use `Forwarding Refs`. https://reactjs.org/docs/forwarding-refs.html – Yassine Sedrani Dec 28 '22 at 11:26
  • @YassineSedrani good job. Works like a charm! – Hiszpan May 29 '23 at 12:25
6

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

maeertin
  • 421
  • 1
  • 4
  • 6
5

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'));
Robin Hossain
  • 711
  • 9
  • 12
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>
Pedram
  • 15,766
  • 10
  • 44
  • 73
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>
  );
1

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;
hamza liaqat
  • 128
  • 1
  • 6
1

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

Use id instead of class

 `navigation={{
     prevEl: "#prev_slide",
     nextEl: "#next_slide",
  }}`
0

For react / ts

navigation={true}
onNavigationNext={(swiper: any) => (navEvent(swiper, "N"))}
onNavigationPrev={(swiper: any) => (navEvent(swiper, "P"))}
Rifat Islam
  • 51
  • 1
  • 3
0

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>
  );
reda baha
  • 49
  • 4
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>
Alec
  • 2,432
  • 2
  • 18
  • 28