Build a carousel
mediumUI Components+210 XP
Build a 4-slide carousel that automatically advances every second.
Prev/Next buttons and dot indicators should navigate immediately, independent of the auto-advance timer. Hovering the carousel must pause the auto-advance; moving the mouse away resumes it.
Examples
// Initial render: active-index = 0 ("Slide A")
// After 1000ms → active-index = 1
// After another 1000ms → 2 ... → 3 ... → wraps to 0.
// Click <button data-testid="next" />
// → index moves immediately, independent of the timer.
// Hover the carousel container
// → auto-advance pauses; mouse out resumes it.Timer runs on an interval; user actions preempt it without resetting the clock.
const pausedRef = useRef(false);
useEffect(() => {
const id = setInterval(() => {
if (!pausedRef.current) {
setIndex((i) => (i + 1) % SLIDES.length);
}
}, 1000);
return () => clearInterval(id);
}, []);A ref flag toggled in `onMouseEnter`/`onMouseLeave` avoids re-subscribing the interval on every hover.
What it has to do
- A `data-testid="carousel"` container wraps the whole thing.
- A `data-testid="active-index"` element shows the current slide index (0-based) as plain text.
- The active slide advances by one automatically every 1000ms, wrapping from the last slide back to the first.
- `data-testid="prev"` / `data-testid="next"` move one slide immediately; `data-testid="dot-N"` jumps directly to slide N.
- While the mouse is over the carousel, automatic advancing is paused; it resumes once the mouse leaves.
Notes
- Wrap with `(index + length) % length` so prev at 0 goes to the last slide and next at last goes to 0 — one expression for both directions.
- Autoplay fights accessibility: WCAG asks for a user-visible pause control and for autoplay to stop when the carousel is focused, not just hovered.
- Clean up the interval in `useEffect`'s return — without it, HMR and unmounts leak timers that keep setting state on a dead tree.
- Prefer a ref for the pause flag rather than state — toggling state on every mouse enter/leave rebuilds the effect and resets the timer, which users feel as a stutter.
Stuck? The react course covers the ideas this problem is built on.
import { useEffect, useState } from "react"; const SLIDES = ["Slide A", "Slide B", "Slide C", "Slide D"]; export default function App() { const [index, setIndex] = useState(0); useEffect(() => { // TODO: this interval should pause while the mouse is hovering the // carousel (see the container below), and resume once it leaves. const id = setInterval(() => { setIndex((i) => (i + 1) % SLIDES.length); }, 1000); return () => clearInterval(id); }, []); return ( <div data-testid="carousel" style={{ fontFamily: "sans-serif", padding: 16, maxWidth: 260 }} > <p> Slide: <span data-testid="active-index">{index}</span> — {SLIDES[index]} </p> <div style={{ display: "flex", gap: 8 }}> <button data-testid="prev" onClick={() => setIndex((i) => (i - 1 + SLIDES.length) % SLIDES.length)}> Prev </button> <button data-testid="next" onClick={() => setIndex((i) => (i + 1) % SLIDES.length)}> Next </button> </div> <div style={{ display: "flex", gap: 6, marginTop: 8 }}> {SLIDES.map((_, i) => ( <button key={i} data-testid={`dot-${i}`} onClick={() => setIndex(i)} style={{ fontWeight: i === index ? "bold" : "normal" }} > {"\u25CF"} </button> ))} </div> </div> ); }
- 1.A `data-testid="carousel"` container wraps the whole thing.
- 2.A `data-testid="active-index"` element shows the current slide index (0-based) as plain text.
- 3.The active slide advances by one automatically every 1000ms, wrapping from the last slide back to the first.
- 4.`data-testid="prev"` / `data-testid="next"` move one slide immediately; `data-testid="dot-N"` jumps directly to slide N.
- 5.While the mouse is over the carousel, automatic advancing is paused; it resumes once the mouse leaves.
5 automated assertions run against your rendered component. The exact assertion names are hidden so you build to the behaviour, not to the test.
5 tests · +210 XP
Discussion
Sign in to postSign in to join the conversation.Sign in
No comments yet. Be the first to say something.