I'm trying to build a carousel with a cover flow effect. I'm rendering three items in the screen, looping through a list of items until the restart. The problem I'm getting is that the transition is not occurring as expected. When the user wants to check the next image, the middle carousel item should transition to the first position, the first one to the last, and the last to the middle. But the last element is not transitioning, it looks like that it's only being rendered at the screen without transitions.
Carousel.tsx:
type CarouselProps = {
children:
| React.ReactElement<CarouselItemProps>
| React.ReactElement<CarouselItemProps>[];
className?: React.HTMLAttributes<HTMLDivElement>["className"];
};
const Carousel = ({ children, className }: CarouselProps) => {
const carouselItems = Children.toArray(children);
const [currentIndex, setCurrentIndex] = useState<number>(0);
const prevIndex =
(currentIndex - 1 + carouselItems.length) % carouselItems.length;
const nextIndex = (currentIndex + 1) % carouselItems.length;
const getCarouselItemsToShow = () => {
if (carouselItems.length < 3) return carouselItems;
const indexes = [prevIndex, currentIndex, nextIndex];
return indexes.map((index) => carouselItems[index]);
};
const handleClickPrev = () => {
setCurrentIndex(
(prevState) =>
(prevState - 1 + carouselItems.length) % carouselItems.length
);
};
const handleClickNext = () => {
setCurrentIndex((prevState) => (prevState + 1) % carouselItems.length);
};
return <div className="relative flex align-items h-[279px] mb-[20px] border border-white *:absolute *:top-[50%] *:-translate-y-1/2 *:-translate-x-1/2 *:transition-all *:duration-1000">
{getCarouselItemsToShow().map((item, index) => {
return React.cloneElement(
item as React.ReactElement<CarouselItemProps>,
{
className:
index === 1
? "w-[419px] left-[50%] z-10"
: "w-[297px] opacity-80 first:left-[30%] last:left-[70%]",
}
);
})}
</div>
[...]
}
CarouselItem.tsx:
export type CarouselItemProps = {
imgSrc: string;
description: string;
className?: React.HTMLAttributes<HTMLDivElement>["className"];
};
const CarouselItem = ({ imgSrc, className }: CarouselItemProps) => {
return (
<Image
src={imgSrc}
alt=""
width={419}
height={275}
className={"rounded-[40px] " + className}
/>
);
};
export default CarouselItem;
App.tsx:
[...]
<Carousel>
<CarouselItem
imgSrc={data.jobs[0].projects[0].imageSrc}
description={data.jobs[0].projects[0].description}
/>
<CarouselItem
imgSrc={data.jobs[0].projects[1].imageSrc}
description={data.jobs[0].projects[1].description}
/>
<CarouselItem
imgSrc={data.jobs[0].projects[2].imageSrc}
description={data.jobs[0].projects[2].description}
/>
</Carousel>
[...]
The issue with this code is, even with the same key being used on the component, React will rerender the components (I think that they will be removed and added again in the DOM) because they are being created in different positions of the DOM.
So, to solve this issue, I kept the keys as the items indexes, but, for showing them on the UI, I ordered them, so I can render them all at the same position in the DOM, keeping the transition.
Updated Carousel component: