I'm trying to build a carousel component in my React project - I'm actually trying to leverage an existing carousel component written in pure JS, using Refs to make it work in React, rather than modifying it directly DOM. I'm running into a problem when I try to initialize the carousel by marking the first slide in the sequence as the current slide (with className current-slide). I put this action in a useEffect hook where {topMovies} is a dependency. {topMovies} is an array of objects passed to the carousel component via prop, so when topMovies changes (i.e. loads), useEffect should run. This works - after loading, the first slide in the carousel is given the classname 'current-slide'.
However, the second piece of code (nextSlide and moveToSlide) is the beginning of the function that selects the next slide. Pass the track Ref (i.e. the container of all slides) to the function so that the child element with className 'current-slide' can be selected and its className removed. This also works - but after a short delay (~1 second) the className 'current-slide' reappears.
const Carouselv2 = ({topMovies}) => { //Refs const trackRef = useRef(); const nextBtnRef = useRef(); const prevBtnRef = useRef(); const navDotsRef = useRef(); //Initialise carousel upon prop load const [isLoading, setIsLoading] = useState(true); useEffect(() => { if (topMovies && setIsLoading) { const slides = Array.from(trackRef.current.children); slides[0].classList.add('current-slide'); //Mark first slide as visible const slideWidth = slides[0].getBoundingClientRect().width; const setSlidePosition = (slide, index) => { slide.style.left = slideWidth * index 'px'; } slides.forEach(setSlidePosition); //Arrange slides alongside each other setIsLoading(false); } }, [topMovies]); //Button functionality const moveToSlide = (currentSlide, targetSlide) => { //trackRef.current.style.transform = 'translateX(-' targetSlide.style.left ')'; currentSlide.classList.remove('current-slide'); //targetSlide.classList.add('current-slide'); } const updateDots = () => { } const responsiveBtns = () => { } const prevSlide = (e) => { } const nextSlide = (e) => { const currentSlide = trackRef.current.querySelector('.current-slide'); const nextSlide = trackRef.current.children.nextElementSibling; moveToSlide(currentSlide, nextSlide); } return (); }{topMovies && topMovies.map((movie) => (
- ))}
{movie.title}
{topMovies && Array.apply(0, Array(topMovies.length)).map(() => { return })}
My initial thought was that for some reason, changing className caused useEffect to fire, so I added an if statement with an isLoading condition. But that didn't work, so I don't think the bug comes from the useEffect hook.
Very confused by this, but this is my first time using Refs, so it's entirely possible I'm missing something very obvious. Thank you for your help!
You are mixing DOM created using React and DOM manipulated using pure Javascript. You can't do this, it will just break the DOM/VDOM relationship.
If you want to avoid moving the entire component to work the way React does, then just use Ref on an empty element, the benefit is that you get the benefits of component mounting and unmounting.
Then you can pretty much copy-paste the Javascript code and just put it all inside
useEffect
.For example:
Below I have created two buttons, one controlled entirely by React and the other by JS, when you click on it it will just toggle the
pink-button
class. I've also placed a checkbox to flip the order of the buttons to cause a re-render to show that the JS version's state is not corrupted, make sure to use thekey
attribute when doing this to let React know it's the same components. The main point here is to show that React will now no longer interfere with this button, it is now entirely your responsibility. Hope this helps you understand how to mix React with pure JS.