I am experiencing a very weird flickering (glitch) when using the combination of
But ONLY in the combination of these three!
Here is the minimal reproducable code:
carousel.js
import styles from './carousel.module.scss'
import { useEffect, useRef, useState } from 'react';
export default function Carousel() {
const [currentScollPos, setCurrentScrollPos] = useState(0)
const carouselRef = useRef()
useEffect(() => {
const carouselScrollUpdate = (e) => {
setCurrentScrollPos(e.target.scrollLeft)
}
carouselRef?.current?.addEventListener('scroll', carouselScrollUpdate, { passive: true })
return () => {
carouselRef?.current?.removeEventListener('scroll', carouselScrollUpdate, { passive: true })
}
}, [carouselRef])
const Slide = () => <div className={styles.carouselSlide}>Test Sub</div>
return (
<div className={styles.carouselInnerContainer} ref={carouselRef}>
<div className={styles.carouselSlide}>Test1</div>
<div className={styles.carouselSlide}>Test2</div>
<div className={styles.carouselSlide}>Test3</div>
<Slide />
</div>
)
}
carousel.module.scss
.carouselInnerContainer {
display: flex;
flex-wrap: nowrap;
overflow-x: scroll;
scroll-snap-type: x mandatory;
}
.carouselSlide {
flex: 0 0 auto;
width: 50%;
margin-left: 2rem;
background-color: aquamarine;
height: 200px;
scroll-snap-align: center;
}
The flickering will NOT be there if I do ONE of the following:
setCurrentScrollPos(e.target.scrollLeft)
<Slide />
scroll-snap-align: center;
in the CSSAny ideas on that weird behaviour?
The problem occurs when you try update the state every time the scroll position changes
const carouselScrollUpdate = (e) => {
setCurrentScrollPos(e.target.scrollLeft)
}
Each setCurrentScrollPos will cause a renderer in your component, causing it to flicker
Instead set state every time you can observer when the scroll stops using setTimout:
const carouselScrollUpdate = (e) => {
clearInterval(timer);
timer = setTimeout(() => {
console.log('set scroll')
setCurrentScrollPos(e.target.scrollLeft)
}, 500);
}
or just set your state when it satisfy some condition:
const carouselScrollUpdate = (e) => {
if (isNearNextSlide()) {
setCurrentScrollPos(e.target.scrollLeft)
}
}
const isNearNextSlide = () => {
// add logic to satisfy your conditions
}
Edit:
After some testing I saw the problem is the inner Slide component inside and I managed to fix it by moving the component outside the main component, preventing that the component from being recreated when rendering
import styles from './carousel.module.scss'
import { useEffect, useRef, useState } from 'react';
const Slide = () => <div className={styles.carouselSlide}>Test Sub</div>
export default function Carousel() {
const [currentScollPos, setCurrentScrollPos] = useState(0)
const carouselRef = useRef()
useEffect(() => {
const carouselScrollUpdate = (e) => {
setCurrentScrollPos(e.target.scrollLeft)
}
carouselRef?.current?.addEventListener('scroll', carouselScrollUpdate, { passive: true })
return () => {
carouselRef?.current?.removeEventListener('scroll', carouselScrollUpdate, { passive: true })
}
}, [carouselRef])
return (
<div className={styles.carouselInnerContainer} ref={carouselRef}>
<div className={styles.carouselSlide}>Test1</div>
<div className={styles.carouselSlide}>Test2</div>
<div className={styles.carouselSlide}>Test3</div>
<Slide />
</div>
)
}