Also posten on Stackoverflow:
Swiper React | How to create custom navigation/pagination components using React refs?
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>
)
}
To work
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!
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.
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:
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