Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
maeertin opened this issue Oct 5, 2020 · 76 comments

Comments

@maeertin
Copy link

maeertin commented Oct 5, 2020

  • 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!

@nolimits4web
Copy link
Owner

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

@taze-fullstack
Copy link

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

@emrerothzerg
Copy link

@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,
        }}

@rikusen0335
Copy link

rikusen0335 commented Nov 1, 2020

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

@michaelthorne
Copy link

michaelthorne commented Nov 4, 2020

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

@rikusen0335
Copy link

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

@michaelthorne
Copy link

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

@rikusen0335
Copy link

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

@ddtch
Copy link

ddtch commented Dec 14, 2020

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

@ciruz
Copy link

ciruz commented Dec 18, 2020

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

@danvisintainer
Copy link

@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

@ciruz
Copy link

ciruz commented Dec 22, 2020

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

@Amine-AD
Copy link

Amine-AD commented Feb 23, 2021

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

@pzi
Copy link

pzi commented Apr 8, 2021

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 => {
    if (typeof Swiper.params.navigation !== 'boolean') {
      const navigation = Swiper.params.navigation;
      navigation.prevEl = navPrevButton.current;
      navigation.nextEl = navNextButton.current;
    }

    if (typeof Swiper.params.pagination !== 'boolean') {
      Swiper.params.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.

@mistval
Copy link

mistval commented Apr 27, 2021

I was having a hard time getting some of the above examples to work. I ended up using the slidePrev() and slideNext() functions like this:

const swiperRef = React.useRef(null);

return (
  <>
    <div id="previousButton" onClick={() => swiperRef.current.swiper.slidePrev()} />
    <div id="nextButton" onClick={() => swiperRef.current.swiper.slideNext()} />
    <Swiper
      ref={swiperRef}
    >
      <SwiperSlide>
        Slide 1
      </SwiperSlide>
      <SwiperSlide>
        Slide 2
      </SwiperSlide>
    </Swiper>
  </>
)

@St3Ko
Copy link

St3Ko commented Apr 29, 2021

@mistval this works, but you will lose the automatic state handling of the navigation buttons (css classes like .swiper-button-disabled etc).

I use this simple react hook to solve this:

// useSwiperRef.js
import { useState, useRef, useEffect } from 'react';

const useSwiperRef = () => {
    const [wrapper, setWrapper] = useState(null);
    const ref = useRef(null);

    useEffect(() => {
        setWrapper(ref.current);
    }, []);

    return [
        wrapper,
        ref
    ]
};

export default useSwiperRef;

and then use it like this:

import useSwiperRef from 'useSwiperRef.js';

const Slider = () => {

    const [nextEl, nextElRef] = useSwiperRef();
    const [prevEl, prevElRef] = useSwiperRef();

    return (
        <Container>
            <Swiper
                navigation={{
                    prevEl,
                    nextEl,
                }}
            >
                <SwiperSlide>1</SwiperSlide>
                <SwiperSlide>2</SwiperSlide>
                <SwiperSlide>3</SwiperSlide>
            </Swiper>
            <Nav ref={prevElRef} />
            <Nav ref={nextElRef} />
        </Container>
    );
};

you should be able to use this hook with everything ref related, for example with the pagination:

import useSwiperRef from 'useSwiperRef.js';

const Slider = () => {

    const [paginationEl, paginationRef] = useSwiperRef();

    return (
        <Container>
            <Swiper
                pagination={{
                    el: paginationEl
                }}
            >
                <SwiperSlide>1</SwiperSlide>
                <SwiperSlide>2</SwiperSlide>
                <SwiperSlide>3</SwiperSlide>
            </Swiper>
            <Pagination ref={paginationRef} />
        </Container>
    );
};

@meesfrenkelfrank
Copy link

@St3Ko I try your solution with typescript. But on:

           <Swiper
                navigation={{
                    prevEl,
                    nextEl,
                }}
            >

I get the following typescript error on prevEl and nextEl:

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

How to solve this?

@St3Ko
Copy link

St3Ko commented Apr 30, 2021

@meesfrenkelfrank my knowledge about typescript is very limited, so sadly i am not able to help you here 😕. Maybe @pzi solution suits your case better?

@meesfrenkelfrank
Copy link

meesfrenkelfrank commented Apr 30, 2021

@St3Ko yeah solution of @pzi works fine! But I am still curious how to fix the other solution... @St3Ko do you have an idea how to fix above issue?

@taily-khucnaykhongquantrong

@St3Ko I try your solution with typescript. But on:

           <Swiper
                navigation={{
                    prevEl,
                    nextEl,
                }}
            >

I get the following typescript error on prevEl and nextEl:

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

How to solve this?

const useSwiperRef = <T extends HTMLElement>(): [
  T | undefined,
  React.Ref<T>
] => {
  const [wrapper, setWrapper] = useState<T>();
  const ref = useRef<T>(null);

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current);
    }
  }, []);

  return [wrapper, ref];
};

and then we could use it:

const [paginationEle, paginationRef] = useSwiperRef<HTMLDivElement>();

@alikleitcr7
Copy link

alikleitcr7 commented Aug 8, 2021

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

I'm trying to compose the above code (with component refs), though I'm getting the following error:

TypeError: swiper.params.navigation is undefined

and when I define a navigation property with {prevEl,nextEl}, I get:

TypeError: swiper.navigation is undefined

My code:

// init in the constructor
this.prevNavRef = React.createRef();
this.nextNavRef = React.createRef();

// component
 <Swiper
     navigation={{
         prevEl: this.prevNavRef.current,
         nextEl: this.nextNavRef.current,
     }}
     onInit={(swiper) => {
         swiper.params.navigation.prevEl = this.prevNavRef.current;
         swiper.params.navigation.nextEl = this.nextNavRef.current;
         swiper.navigation.init(); // throws error here, navigation is undefined
         swiper.navigation.update();
     }} >... </Swiper>

// custom navigation elements: intended to place outside `<Swiper>`, though I tried to place inside but still the same. 

<div ref={this.prevNavRef} className="swiper-button-prev"></div>
<div ref={this.nextNavRef} className="swiper-button-next"></div>

I'm currently using swiper version 6.8.1

// update

after importing:

import SwiperCore, { Navigation} from 'swiper'
SwiperCore.use([Navigation])

error is bypassed, though the events are not triggered only if these custom elements are inside <Swiper>

I guess my issue is a milestone in v7 Navigation and pagination positioning around slider (not over) 🙏 👍

@joebentaylor1995
Copy link

I have the issue that none of these solutions works if the fade effect is applied to the slider, it triggers once, then never again on click.

@kacluk123
Copy link

kacluk123 commented Sep 3, 2021

Only example with passing ref directly to Swiper works :(

@thongvmdev
Copy link

I was having a hard time getting some of the above examples to work. I ended up using the slidePrev() and slideNext() functions like this:

const swiperRef = React.useRef(null);

return (
  <>
    <div id="previousButton" onClick={() => swiperRef.current.swiper.slidePrev()} />
    <div id="nextButton" onClick={() => swiperRef.current.swiper.slideNext()} />
    <Swiper
      ref={swiperRef}
    >
      <SwiperSlide>
        Slide 1
      </SwiperSlide>
      <SwiperSlide>
        Slide 2
      </SwiperSlide>
    </Swiper>
  </>
)

Thank you so much, this acctualy help me solve my problem!!!!

@usercao
Copy link

usercao commented Sep 16, 2021

The above schemes have type errors in the default typescript project created by CRA and cannot be used. The following is an example [email protected] Workarounds available in version typescript.

import { useRef, useEffect } from "react";
import Swiper from "swiper";
import "swiper/swiper-bundle.css";

function Banner() {
  const domRef = useRef<HTMLDivElement>(null);
  const swiperRef = useRef<Swiper>();
  const ARRAY_LIST: number[] = [1, 2, 3, 4, 5, 6];

  useEffect(() => {
    const mySwiper = new Swiper(domRef.current!, {
      slidesPerView: "auto",
      loop: false,
    });
    swiperRef.current = mySwiper;
    return () => swiperRef.current!.destroy(true, true);
  }, []);

  return (
    <>
      <button onClick={() => swiperRef.current!.slidePrev()}>Prev</button>
      <div className="swiper-container" ref={domRef}>
        <ul className="swiper-wrapper">
          {ARRAY_LIST.map((item) => (
            <li className="swiper-slide" key={item}>
              {item}
            </li>
          ))}
        </ul>
      </div>
      <button onClick={() => swiperRef.current!.slideNext()}>Next</button>
    </>
  );
}

@wojciechkubiak
Copy link

I know it's not perfect solution for typescript users, but if none of previous examples works for you, maybe this one will. I know that using "any" isn't great, but it's still better than ts-ignore.

const Projects = () => {
  const [swipe, setSwipe] = useState<any>();

  return (
    <>
        <button onClick={() => swipe?.slidePrev()}>backward</button>
        <Swiper
          onBeforeInit={(swipper) => setSwipe(swipper)}
        >
          {projects?.map((value: IProjectCard) => {
            return (
              <SwiperSlide key={uuidv4()}>
                <ProjectCard
                  key={uuidv4()}
                  header={value.header}
                />
              </SwiperSlide>
            );
          })}
        </Swiper>
        <button onClick={() => swipe?.slideNext()}>forward</button>
    </>
  );
};

P.S. Do not downvote me, it's my first comment on github. Just say my solution sucks. :D

@LoicHa
Copy link

LoicHa commented Nov 8, 2021

const [swipe, setSwipe] = useState();

I used const [swipe, setSwipe] = useState<SwiperClass>(); Your solution seems decent

@kruzyk
Copy link

kruzyk commented Jul 18, 2022

@markcnunes nice, but what about dynamic changes like adding swiper-button-disabled? I tried on 8.3.1 and this seems to not work...

@markcnunes
Copy link

markcnunes commented Jul 18, 2022

@kruzyk I only used Swiper once so I imagine other people might be able to inform better what is the right approach for this. I don't know what you meant about swiper-button-disabled but see an example below if you want to disable the buttons once Swiper reached the beginning and end of the slides.

import { useState } from "react";
import { Swiper, SwiperSlide, SwiperProps, useSwiper } from "swiper/react"; // Swiper 8.3.1
import "swiper/css";

const CustomNavigation = ({ slot }: { slot: "container-end" }) => {
  const swiper = useSwiper();
  const [slideProgress, setSlideProgress] = useState<number>(0);

  swiper.on("slideChange", (e) => {
    setSlideProgress(e.progress);
  });

  return (
    <div slot={slot}>
      <button onClick={() => swiper.slidePrev()} disabled={slideProgress === 0}>
        Slide to the previous slide
      </button>
      <button onClick={() => swiper.slideNext()} disabled={slideProgress === 1}>
        Slide to the next slide
      </button>
    </div>
  );
};

export default ({ children, ...other }: SwiperProps) => {
  return (
    <Swiper {...other}>
      <SwiperSlide>Slide 1</SwiperSlide>
      <SwiperSlide>Slide 2</SwiperSlide>
      <SwiperSlide>Slide 3</SwiperSlide>
      <CustomNavigation slot="container-end" />
    </Swiper>
  );
};

@captainR0bbo
Copy link

I know it's not perfect solution for typescript users, but if none of previous examples works for you, maybe this one will. I know that using "any" isn't great, but it's still better than ts-ignore.

const Projects = () => {
  const [swipe, setSwipe] = useState<any>();

  return (
    <>
        <button onClick={() => swipe?.slidePrev()}>backward</button>
        <Swiper
          onBeforeInit={(swipper) => setSwipe(swipper)}
        >
          {projects?.map((value: IProjectCard) => {
            return (
              <SwiperSlide key={uuidv4()}>
                <ProjectCard
                  key={uuidv4()}
                  header={value.header}
                />
              </SwiperSlide>
            );
          })}
        </Swiper>
        <button onClick={() => swipe?.slideNext()}>forward</button>
    </>
  );
};

P.S. Do not downvote me, it's my first comment on github. Just say my solution sucks. :D

Thanks! This is working well. Simplest cleanest solution here.

@Anvarmirzo
Copy link

@St3Ko I try your solution with typescript. But on:

           <Swiper
                navigation={{
                    prevEl,
                    nextEl,
                }}
            >

I get the following typescript error on prevEl and nextEl:

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

How to solve this?

const useSwiperRef = <T extends HTMLElement>(): [
  T | undefined,
  React.Ref<T>
] => {
  const [wrapper, setWrapper] = useState<T>();
  const ref = useRef<T>(null);

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current);
    }
  }, []);

  return [wrapper, ref];
};

and then we could use it:

const [paginationEle, paginationRef] = useSwiperRef<HTMLDivElement>();

@St3Ko I try your solution with typescript. But on:

           <Swiper
                navigation={{
                    prevEl,
                    nextEl,
                }}
            >

I get the following typescript error on prevEl and nextEl:

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

How to solve this?

const useSwiperRef = <T extends HTMLElement>(): [
  T | undefined,
  React.Ref<T>
] => {
  const [wrapper, setWrapper] = useState<T>();
  const ref = useRef<T>(null);

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current);
    }
  }, []);

  return [wrapper, ref];
};

and then we could use it:

const [paginationEle, paginationRef] = useSwiperRef<HTMLDivElement>();

TypeScript solution

With the previous solution, the buttons might not work. You need to tweak it a bit. Be sure to initialize the state with null:

const useSwiperRef = <T extends HTMLElement>(): [T | null, React.Ref<T>] => {
  const [wrapper, setWrapper] = useState<T | null>(null)
  const ref = useRef<T>(null)

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current)
    }
  }, [])

  return [wrapper, ref]
}
  const [nextEl, nextElRef] = useSwiperRef<HTMLButtonElement>()
  const [prevEl, prevElRef] = useSwiperRef<HTMLButtonElement>()
...
  <Swiper
        navigation={{
          prevEl,
          nextEl,
        }}
      >
      ...
  </Swiper>
  <Button ref={prevElRef} />
  <Button ref={nextElRef} />
  ...

Work for me...

Perfect!

@TedxMaitama
Copy link

nextElRef

Finally, something that works. Thanks

@Mallikarjunamatla
Copy link

Thank you....struggling for with the given solutions...this worked perfectly..!

@7iomka
Copy link

7iomka commented Sep 5, 2022

@RemyMachado thanks for solution.
One question - how to handle enabled status of this custom navigation buttons?
Now I has it always displayed.

@RemyMachado
Copy link

@RemyMachado thanks for solution. One question - how to handle enabled status of this custom navigation buttons? Now I has it always displayed.

@7iomka
If it doesn't work out of the box, you may try to listen to the current index and apply status styles manually.

    ...
    const [currentIndex, setCurrentIndex] = useState<number>(0)
    ...
     <Swiper
        onSlideChange={(state) => setCurrentIndex(state.activeIndex)}
      >
        ...
     </Swiper>
        ...
     <CustomLeftButton  ref={prevElRef} className={currentIndex === 0 ? "disabled-class" : "enabled-class"} />

I didn't try it, but I hope it will at least guide you toward a solution.

@7iomka
Copy link

7iomka commented Sep 5, 2022

@Razunter my case is much more complicated to resolve.
The problem is when actual slidesPerView (actual - means for current breakpoint) is more than total items length and loop: true.
For this case I need to have disabled (or hidden as a best solution) navigation (with buttons or with swipes)
Maybe this not worked out of the box, because I used a hotfix for looped sliders and SSR (found in issues)

loopAdditionalSlides={items.length - 1} // fix SSR mismatch issue

I created a working demo.
Please, see what can be done.
Thanks.

https://stackblitz.com/edit/node-nqguhs?file=src%2Fshared%2Fui%2Fcarousel%2Fcarousel.component.tsx
(just wait for the modules to be installed in the demo)

@pzi
Copy link

pzi commented Sep 6, 2022

can this remain on topic of navigation/pagination refs please... if you have a different issue, you may want to create a new one for visibility.

@7iomka 7iomka mentioned this issue Sep 7, 2022
6 tasks
@doobee98
Copy link

doobee98 commented Sep 18, 2022

Typescript Solution without useState

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

const swiperRef = useRef<SwiperCore>()

return (
  <Swiper
     onBeforeInit={(swiper) => {
       swiperRef.current = swiper
     }}
  >
    {.... my slides components}
    <CustomPrevButton onClick={() => swiperRef.current?.slidePrev()} />
    <CustomNextButton onClick={() => swiperRef.current?.slideNext()} />
  </Swiper>
)

simply this works for me without type error.

@Mustafa-Zahedi

This comment was marked as off-topic.

@Mustafa-Zahedi
Copy link

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

try this
(swiper.params.navigation as NavigationOptions).prevEl = prevRef.current;

@datvnn
Copy link

datvnn commented Oct 14, 2022

If you are using Typescript then use the TypeScript solution by @RemyMachado, it works perfectly. You can also try with js.
You can do with it: customize buttons css, add dynamic classes like swiper-button-disabled.
Example: To add css that hides the Next button when at the bottom of the slide and hides the Prev button when at the bottom of the slide, see the following code:

Original code from @RemyMachado

TypeScript solution
With the previous solution, the buttons might not work. You need to tweak it a bit. Be sure to initialize the state with null:

const useSwiperRef = <T extends HTMLElement>(): [T | null, React.Ref<T>] => {
  const [wrapper, setWrapper] = useState<T | null>(null)
  const ref = useRef<T>(null)

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current)
    }
  }, [])

  return [wrapper, ref]
}
  const [nextEl, nextElRef] = useSwiperRef<HTMLButtonElement>()
  const [prevEl, prevElRef] = useSwiperRef<HTMLButtonElement>()
...
  <Swiper
        navigation={{
          prevEl,
          nextEl,
        }}
      >
      ...
  </Swiper>
  <Button ref={prevElRef} />
  <Button ref={nextElRef} />
  ...

You just need to add nav parameter like below and customize your css

...
  <Swiper
        navigation={{
          prevEl,
          nextEl,
          disabledClass: 'hidden',   // <-- css classes are added automatically
        }}
      >
      ...
  </Swiper>
  <button ref={prevElRef}  className={`css for Prev button`}>Prev</button>
  <button ref={nextElRef} className={`css for Next button`}>Next</button>
  ...

@chitalian
Copy link

None of these solutions worked for me except this one I found on stack overflow
https://stackoverflow.com/questions/70324190/custom-arrow-swiper-slider-next-js-sass/74165878#74165878

import React, { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperCore } from 'swiper/types';

import "swiper/css";

const swiperRef = useRef<SwiperCore>();  

... 
      <Swiper
        slidesPerView={1}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
...

I hope this helps someone!

Typescript solution

@codefromahmad
Copy link

Worked for me Thanks

@muhammedrepo
Copy link

None of these solutions worked for me except this one I found on stack overflow https://stackoverflow.com/questions/70324190/custom-arrow-swiper-slider-next-js-sass/74165878#74165878

import React, { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperCore } from 'swiper/types';

import "swiper/css";

const swiperRef = useRef<SwiperCore>();  

... 
      <Swiper
        slidesPerView={1}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
...

I hope this helps someone!

Typescript solution

This works for me thank you

@elielgomes
Copy link

Muito obrigado, utilizei a solução citada a cima e deu certo, dessa forma conseguir criar botões de paginação personalizados

@rudestewing
Copy link

  • 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!

thank you you saved me @maeertin
🙏

@mobak88
Copy link

mobak88 commented Mar 9, 2023

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 => {
    if (typeof Swiper.params.navigation !== 'boolean') {
      const navigation = Swiper.params.navigation;
      navigation.prevEl = navPrevButton.current;
      navigation.nextEl = navNextButton.current;
    }

    if (typeof Swiper.params.pagination !== 'boolean') {
      Swiper.params.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.

I had to make some modifications to make it work, im new to TS so open to feedback

const onBeforeInit = (Swiper: SwiperCore): void => {
    if (typeof Swiper.params.navigation !== "boolean") {
      const navigation = Swiper.params.navigation;
      if (navigation !== undefined) {
        navigation.prevEl = swiperNavPrevRef.current;
        navigation.nextEl = swiperNavNextRef.current;
      }
    }
  };

@lucianobarauna
Copy link

@mobak88 thanks !!
I used your solution in Swiper 9.1 and it worked great.

@albertfredholm
Copy link

albertfredholm commented Mar 21, 2023

None of these solutions worked for me except this one I found on stack overflow https://stackoverflow.com/questions/70324190/custom-arrow-swiper-slider-next-js-sass/74165878#74165878

import React, { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperCore } from 'swiper/types';

import "swiper/css";

const swiperRef = useRef<SwiperCore>();  

... 
      <Swiper
        slidesPerView={1}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
...

I hope this helps someone!
Typescript solution

This works for me thank you

This works for me! Thank you very much, @coolzyte @chitalian

@tinemuz
Copy link

tinemuz commented May 6, 2023

Thank you! This works well.

@mistdev6
Copy link

I know it's not perfect solution for typescript users, but if none of previous examples works for you, maybe this one will. I know that using "any" isn't great, but it's still better than ts-ignore.

const Projects = () => {
  const [swipe, setSwipe] = useState<any>();

  return (
    <>
        <button onClick={() => swipe?.slidePrev()}>backward</button>
        <Swiper
          onBeforeInit={(swipper) => setSwipe(swipper)}
        >
          {projects?.map((value: IProjectCard) => {
            return (
              <SwiperSlide key={uuidv4()}>
                <ProjectCard
                  key={uuidv4()}
                  header={value.header}
                />
              </SwiperSlide>
            );
          })}
        </Swiper>
        <button onClick={() => swipe?.slideNext()}>forward</button>
    </>
  );
};

P.S. Do not downvote me, it's my first comment on github. Just say my solution sucks. :D

This worked amazingly, thanks :)

@Arz-zun
Copy link

Arz-zun commented Aug 18, 2023

Another way to solve this problem:

// use the `useState` hook instead of `useRef`
const [prevEl, setPrevEl] = useState<HTMLElement | null>(null)
const [nextEl, setPrevEl] = useState<HTMLElement | null>(null)
<Swiper navigation={{ prevEl, nextEl }}>
// some slides
</Swiper>
<div ref={(node) => setPrevEl(node)}>prev</div>
<div ref={(node) => setNextEl(node)}>next</div>

This works for me

@marselbairam
Copy link

None of these solutions worked for me except this one I found on stack overflow https://stackoverflow.com/questions/70324190/custom-arrow-swiper-slider-next-js-sass/74165878#74165878

import React, { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperCore } from 'swiper/types';

import "swiper/css";

const swiperRef = useRef<SwiperCore>();  

... 
      <Swiper
        slidesPerView={1}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
...

I hope this helps someone!

Typescript solution

"swiper": "^8.2.6"

This works for me, thanks

@beforedisappear
Copy link

beforedisappear commented Apr 6, 2024

This works for me if the navigation does not respond to clicks on first mouting.

           <Swiper
                navigation={{ prevEl: prevRef.current, nextEl: nextRef.current }}
                modules={[Navigation]}
                onBeforeInit={(swiper) => {
                       if (typeof swiper.params.navigation === "object") {
                           swiper.params.navigation.prevEl = prevRef.current;
                           swiper.params.navigation.nextEl = nextRef.current;
                       }
                }}
            >

           ....
       
        <button id={styles.prev_btn} ref={prevRef}>
       </button>
       <button id={styles.next_btn} ref={nextRef}>
       </button>
         

           </Swiper>

@tylerrrkd
Copy link

tylerrrkd commented Jul 12, 2024

If you are using Typescript then use the TypeScript solution by @RemyMachado, it works perfectly. You can also try with js. You can do with it: customize buttons css, add dynamic classes like swiper-button-disabled. Example: To add css that hides the Next button when at the bottom of the slide and hides the Prev button when at the bottom of the slide, see the following code:

Original code from @RemyMachado

TypeScript solution With the previous solution, the buttons might not work. You need to tweak it a bit. Be sure to initialize the state with null:

const useSwiperRef = <T extends HTMLElement>(): [T | null, React.Ref<T>] => {
  const [wrapper, setWrapper] = useState<T | null>(null)
  const ref = useRef<T>(null)

  useEffect(() => {
    if (ref.current) {
      setWrapper(ref.current)
    }
  }, [])

  return [wrapper, ref]
}
  const [nextEl, nextElRef] = useSwiperRef<HTMLButtonElement>()
  const [prevEl, prevElRef] = useSwiperRef<HTMLButtonElement>()
...
  <Swiper
        navigation={{
          prevEl,
          nextEl,
        }}
      >
      ...
  </Swiper>
  <Button ref={prevElRef} />
  <Button ref={nextElRef} />
  ...

You just need to add nav parameter like below and customize your css

...
  <Swiper
        navigation={{
          prevEl,
          nextEl,
          disabledClass: 'hidden',   // <-- css classes are added automatically
        }}
      >
      ...
  </Swiper>
  <button ref={prevElRef}  className={`css for Prev button`}>Prev</button>
  <button ref={nextElRef} className={`css for Next button`}>Next</button>
  ...

Thanks for your solution, friend. And here I am using the dynamical way to render Swiper. If you also need render dynamically, you could try this code below.

const useSwiperRef = <T extends HTMLElement>(): [
  T | null,
  (instance: T | null) => void
] => {
  const [wrapper, setWrapper] = useState<T | null>(null);

  const ref = useCallback((node: T | null) => {
    if (node !== null) {
      setWrapper(node);
    }
  }, []);

  return [wrapper, ref];
};

@onyedikachi23
Copy link

onyedikachi23 commented Aug 29, 2024

None of these solutions worked for me except this one I found on stack overflow stackoverflow.com/questions/70324190/custom-arrow-swiper-slider-next-js-sass/74165878#74165878

import React, { useRef } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Swiper as SwiperCore } from 'swiper/types';

import "swiper/css";

const swiperRef = useRef<SwiperCore>();  

... 
      <Swiper
        slidesPerView={1}
        onBeforeInit={(swiper) => {
          swiperRef.current = swiper;
        }}
      >
        <SwiperSlide>Slide 1</SwiperSlide>
        <SwiperSlide>Slide 2</SwiperSlide>
      </Swiper>
      <button onClick={() => swiperRef.current?.slidePrev()}>Prev</button>
      <button onClick={() => swiperRef.current?.slideNext()}>Next</button>
...

I hope this helps someone!
Typescript solution

"swiper": "^8.2.6"

This works for me, thanks

After endless scrolling here and on Stack Overflow, and endless code changes, this solution works for me.

Thanks @marselbairam for a being a 2024 person to point out this straightforward solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests