-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
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
Comments
Not possible by passing directly as 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:
|
@taze-fullstack you can try this
in swiper
|
Could anybody show me full code? (and if you could Typescript?)
|
@rikusen0335 @nolimits4web I'd also appreciate some React/TypeScript help here: The example below works, but only because of the 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. |
@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
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 |
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.
|
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 |
@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
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 => {
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 |
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>
</>
) |
@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>
);
}; |
@St3Ko I try your solution with typescript. But on:
I get the following typescript error on
How to solve this? |
@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? |
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>(); |
I'm trying to compose the above code (with component refs), though I'm getting the following error:
and when I define a
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 // 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 I guess my issue is a milestone in v7 Navigation and pagination positioning around slider (not over) 🙏 👍 |
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. |
Only example with passing ref directly to Swiper works :( |
Thank you so much, this acctualy help me solve my problem!!!! |
The above schemes have type errors in the default 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>
</>
);
} |
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.
P.S. Do not downvote me, it's my first comment on github. Just say my solution sucks. :D |
I used |
@markcnunes nice, but what about dynamic changes like adding |
@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 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>
);
}; |
Thanks! This is working well. Simplest cleanest solution here. |
Perfect! |
Finally, something that works. Thanks |
Thank you....struggling for with the given solutions...this worked perfectly..! |
@RemyMachado thanks for solution. |
@7iomka ...
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. |
@Razunter my case is much more complicated to resolve. loopAdditionalSlides={items.length - 1} // fix SSR mismatch issue I created a working demo. https://stackblitz.com/edit/node-nqguhs?file=src%2Fshared%2Fui%2Fcarousel%2Fcarousel.component.tsx |
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. |
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. |
This comment was marked as off-topic.
This comment was marked as off-topic.
try this |
If you are using Typescript then use the TypeScript solution by @RemyMachado, it works perfectly. You can also try with js. Original code from @RemyMachado TypeScript solution 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>
... |
None of these solutions worked for me except this one I found on stack overflow 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 |
Worked for me Thanks |
This works for me thank you |
Muito obrigado, utilizei a solução citada a cima e deu certo, dessa forma conseguir criar botões de paginação personalizados |
thank you you saved me @maeertin |
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;
}
}
}; |
@mobak88 thanks !! |
This works for me! Thank you very much, @coolzyte @chitalian |
Thank you! This works well. |
This worked amazingly, thanks :) |
This works for me |
"swiper": "^8.2.6" This works for me, thanks |
This works for me if the navigation does not respond to clicks on first mouting.
|
Thanks for your solution, friend. And here I am using the dynamical way to render 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];
}; |
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. |
Also posten on Stackoverflow:
Swiper React | How to create custom navigation/pagination components using React refs?
What you did
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".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:
Thanks in advance!
The text was updated successfully, but these errors were encountered: