I’m using the ShadCN Carousel in my Next.js app and trying to integrate it with server-side paginated API data.
The issue I’m facing is that the scrolling is not smooth—whenever new data is loaded, I notice a UI jump or flicker. It seems like the carousel recalculates the item sizes, causing an abrupt shift instead of a seamless transition.
Has anyone experienced this before? Is there a known solution or best practice for handling pagination smoothly in the ShadCN carousel?
Thanks in advance!
const PaginatedCarousel = ({ initialData, type }) => {
const [data, setData] = useState(initialData?.results || [])
const [currentPage, setCurrentPage] = useState(1)
const [carouselApi, setCarouselApi] = useState(null)
const [isPending, startTransition] = useTransition()
const page_count = initialData?.page_count
useEffect(() => {
if (!carouselApi) return
const handleSelect = async () => {
const currentIndex = carouselApi.selectedScrollSnap()
const totalSlides = carouselApi.scrollSnapList().length
if (
currentIndex >= totalSlides - 3 &&
currentPage < page_count &&
!isPending
) {
const newPage = currentPage + 1
startTransition(async () => {
const newData = await fetchMoreCourses({ type, page: newPage })
if (newData?.results?.length) {
setData((prev) => [...prev, ...newData.results])
setCurrentPage(newPage)
}
})
}
}
carouselApi.on('select', handleSelect)
return () => {
carouselApi.off('select', handleSelect)
}
}, [carouselApi, data, currentPage, type, page_count, isPending])
return (
<div className="flex flex-col gap-12 w-11/12">
<div className="flex items-center justify-between">
<span className="m:text-2xl text-xl text-neutrals-50 font-clash not-italic leading-9 font-extramedium">
{MAPPED_TITLE[type]}
</span>
<div className="flex items-center gap-5">
<Image
width={11}
height={18}
src={'/images/prevArrow.svg'}
alt={'prev-arrow'}
className={'cursor-pointer'}
onClick={() => carouselApi && carouselApi.scrollPrev()}
/>
<Image
width={11}
height={18}
src={'/images/nextArrow.svg'}
alt={'next-arrow'}
className={'cursor-pointer'}
onClick={() => carouselApi && carouselApi.scrollNext()}
/>
</div>
</div>
<div className="w-full overflow-hidden">
<Carousel className="w-full relative" setApi={setCarouselApi}>
<CarouselContent className="flex space-x-2">
{data.map((course) => (
<CardsOrchestrator course={course} type={type} key={course.id} />
))}
{isPending && <SkeletonCard type={type} />}
</CarouselContent>
</Carousel>
</div>
</div>
)
}