How to build an Auto-Playing Slideshow with React
Tags: css, javascript, react, tutorial
Published: 2020-11-26 19:43:46

> This article was originally published on [Tinloof](https://tinloof.com/blog/how-to-build-an-auto-play-slideshow-with-react/).

<figure>
  <img loading="lazy"
    src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605298513/blog/automated-slideshow-react/Slideshow%20GIF.gif"
  />
  <figcaption>Final result</figcaption>
</figure>

In this article we'll build an auto-playing slideshow using React.

The article is divided in two sections:

1. The trick
2. Functionality

Here's the final result (Codepen link [here](https://codepen.io/bnsddk/pen/dyXaNod) and Codesandbox link [here](https://codesandbox.io/s/automated-slideshow-h9h4x?file=/src/Slideshow/index.js)):

{% codepen https://codepen.io/twhite96/pen/dyXaNod %}

# The trick

Our Slideshow component is divided in three containers:

- slideshow
- slideshowSlider
- slide

Here's a sketch to visualize the structure:

![https://res.cloudinary.com/https-tinloof-com/image/upload/v1605382983/blog/automated-slideshow-react/position_1_itid9p.png](https://res.cloudinary.com/https-tinloof-com/image/upload/v1605382983/blog/automated-slideshow-react/position_1_itid9p.png)

What is visible to the user is what is shown within the red box (the container *slideshow*). 

After a couple of seconds, the container *slideshowSlider* will move to the left to expose the next container *slide*, as shown below:

![https://res.cloudinary.com/https-tinloof-com/image/upload/v1605382983/blog/automated-slideshow-react/position_2_lv9t56.png](https://res.cloudinary.com/https-tinloof-com/image/upload/v1605382983/blog/automated-slideshow-react/position_2_lv9t56.png)

As you can imagine, after a couple of seconds the container *slideshowSlider* will move again and what will be shown to the user is the yellow container *slide.* 

A couple of seconds later, the container *slideshowSlider* will go back to its original position and we'll see the blue container *slide* again.

And so on.

Here's the corresponding markup:

```jsx
function Slideshow() {
  return (
    <div className="slideshow">
      <div className="slideshowSlider">
        <div className="slide"></div>
      </div>
    </div>
  );
}
```

## Step 1: show colored slides

Let's use the previous markup to show a few colored slides:

```jsx
const colors = ["#0088FE", "#00C49F", "#FFBB28"];

function Slideshow() {
  return (
    <div className="slideshow">
      <div className="slideshowSlider">
        {colors.map((backgroundColor, index) => (
          <div className="slide" key={index} style={{ backgroundColor }}/>
        ))}
      </div>
    </div>
  );
}

```

## Step 2: styling

First, let's style the parent container *slideshow*:

```css
/* Slideshow */

.slideshow {
  margin: 0 auto;
  overflow: hidden;
  max-width: 500px;
}
```
We center it with `margin: 0 auto`, set a `max-width` to it and make the content flowing outside the element's box invisible with `overflow:hidden`.

Now let's style *slide*:

```css
/* Slideshow */

.slideshow {
  margin: 0 auto;
  overflow: hidden;
  max-width: 500px;
}

.slide {
  height: 400px;
  width: 100%;
  border-radius: 40px;
}
```

We get:

<figure>
<img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605458704/blog/automated-slideshow-react/Screenshot_2020-11-15_at_17.34.37_q5jmzn.png"/><figcaption>Slides are on top of each other
</figcaption>
</figure>

We don't want to have the slides one on top of each other, but we want them one next to each other. 

For that, we'll set `display: inline-block` since `div`s are set with `display:block` by default, which makes them start in a new line:

```css
/* Slideshow */

.slideshow {
  margin: 0 auto;
  overflow: hidden;
  max-width: 500px;
}

.slide {
  display: inline-block;

  height: 400px;
  width: 100%;
  border-radius: 40px;
}
```
We get:

<figure>
<img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605458655/blog/automated-slideshow-react/Screenshot_2020-11-15_at_17.39.22_t2yphh.png"/>
  <figcaption>Not much changed
</figcaption>
</figure>

Not much changed, and it still looks like we have `display:block` and that is because `div`s wrap to the next line when there's no space in the container. Because our slides take 100% of the *slideshow*'s width each, there is no space in the container.

We'll use `white-space: nowrap` in the *slides* container so we never wrap to the next line:

```css
/* Slideshow */

.slideshow {
  margin: 0 auto;
  overflow: hidden;
  max-width: 500px;
}

.slideshowSlider {
  white-space: nowrap;
}

.slide {
  display: inline-block;

  height: 400px;
  width: 100%;
  border-radius: 40px;
}
```

We get:

 <figure>
<img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605386827/blog/automated-slideshow-react/Screenshot_2020-11-14_at_21.46.56_z0t1uf.png"/><figcaption>No more wrapping to the next line
</figcaption>
</figure>

We no longer have `div`s wrapping to the next line.

## Step 3: create the buttons

Now that we have the structure of the color containers, let's add the buttons (dots) beneath them.

We'll map again through the array again and add a dot for each array element:

```jsx
const colors = ["#0088FE", "#00C49F", "#FFBB28"];

function Slideshow() {
  return (
    <div className="slideshow">
      <div className="slideshowSlider">
        {colors.map((backgroundColor, index) => (
          <div
            className="slide"
            key={index}
            style={{ backgroundColor }}
          ></div>
        ))}
      </div>

      <div className="slideshowDots">
        {colors.map((_, idx) => (
          <div key={idx} className="slideshowDot"></div>
        ))}
      </div>
    </div>
  );
}
```

Let's style the buttons:

```css
/* Slideshow */

.slideshow {
  margin: 0 auto;
  overflow: hidden;
  max-width: 500px;
}

.slideshowSlider {
  white-space: nowrap;
}

.slide {
  display: inline-block;

  height: 400px;
  width: 100%;
  border-radius: 40px;
}

/* Buttons */

.slideshowDots {
  text-align: center;
}

.slideshowDot {
  display: inline-block;
  height: 20px;
  width: 20px;
  border-radius: 50%;

  cursor: pointer;
  margin: 15px 7px 0px;

  background-color: #c4c4c4;
}
```

We get:

 <figure>
 <img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605437631/blog/automated-slideshow-react/Screenshot_2020-11-15_at_11.53.27_opmh2w.png"/>
<figcaption>Color container and buttons (dots) are ready
</figcaption>
</figure>

We are done with the structure and the styling. Let's now focus on the functionality of the slideshow.

# Functionality

If you noticed in the sketch above, we moved the position of *slideshowSlider* to the left to display different color containers in its parent `div` slideshow. 

Notice how the blue container below is moving to the left as a result of *slideshowSlider* moving:

<figure>
<img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605388243/blog/automated-slideshow-react/div_slideshow_moving_llq5ok.gif"/>
<figcaption>Logic behind the slideshow's functionality</figcaption>
</figure>

To achieve this, we will use `transform: translate3d` (or you can use `transform: translate`).

What we essentially want to do here is move the position of *slideshowSlider* by 0% when index is 0, -100% when index is 1 and by -200% when index is 2.

To keep tracking of the currently displayed index, we use useState and we initialize it with 0:

```jsx
const colors = ["#0088FE", "#00C49F", "#FFBB28"];

function Slideshow() {
  const [index, setIndex] = React.useState(0);

  return (
    <div className="slideshow">
      <div
        className="slideshowSlider"
        style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
      >
        {colors.map((backgroundColor, index) => (
          <div
            className="slide"
            key={index}
            style={{ backgroundColor }}
          ></div>
        ))}
      </div>

      <div className="slideshowDots">
        {colors.map((_, idx) => (
          <div key={idx} className="slideshowDot"></div>
        ))}
      </div>
    </div>
  );
}
```

To make the slideshow automatic, we change the index every 2,5 seconds using [setTimeout](https://www.w3schools.com/jsref/met_win_settimeout.asp). 

Since this is a [side effect](https://reactjs.org/docs/hooks-effect.html), we do so with useEffect.

Since we want to perform this action every time the index changes, we put the index in the dependency array passed to useEffect:

```jsx
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 2500;

function Slideshow() {
  const [index, setIndex] = React.useState(0);

  React.useEffect(() => {
    setTimeout(
      () =>
        setIndex((prevIndex) =>
          prevIndex === colors.length - 1 ? 0 : prevIndex + 1
        ),
      delay
    );

    return () => {};
  }, [index]);

  return (
    <div className="slideshow">
      <div
        className="slideshowSlider"
        style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
      >
        {colors.map((backgroundColor, index) => (
          <div
            className="slide"
            key={index}
            style={{ backgroundColor }}
          ></div>
        ))}
      </div>

      <div className="slideshowDots">
        {colors.map((_, idx) => (
          <div key={idx} className="slideshowDot"></div>
        ))}
      </div>
    </div>
  );
}

```

Every 2500 milliseconds (2.5 seconds), the setIndex function will be called. It will first check if the current index is equal to the array's length minus one, that way it knows if to move to the next index or start from scratch.

For example, if we're at index 0, which is not equal to the array length minus one (3-1=2), it will update the index to be 1.

However, if we're at index 2, which is equal to the array's length minus one (3-1=2), it will update the index to be 0.

We get:

<figure>
<img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605391174/blog/automated-slideshow-react/slideshow_moving_not_smooth_no_buttons_gusunt.gif"/>
<figcaption>Slideshow is working
</figcaption>
</figure>

We want a smoother transition, so let's go back to the CSS and add `transition` to *slideshowSlider*:

```css
/* Slideshow */

.slideshow {
  margin: 0 auto;
  overflow: hidden;
  max-width: 500px;
}

.slideshowSlider {
  white-space: nowrap;
  transition: ease 1000ms;
}

.slide {
  display: inline-block;

  height: 400px;
  width: 100%;
  border-radius: 40px;
}

/* Buttons */

.slideshowDots {
  text-align: center;
}

.slideshowDot {
  display: inline-block;
  height: 20px;
  width: 20px;
  border-radius: 50%;

  cursor: pointer;
  margin: 15px 7px 0px;

  background-color: #c4c4c4;
}
```

Now it's better:

<figure>
<img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605389659/blog/automated-slideshow-react/moving_slide_no_button_ardxbe.gif"/><figcaption>Slideshow animation is better with `transition`
</figcaption>
</figure>

The slideshow works, but the buttons are not reflecting the active slide. 

So far, all our buttons are grey. Let's add a className "active" to color in purple the button corresponding to the current slide index (`index` state value).

While mapping through the colors, we check if the index of the slide is equal to the index of the dot, if it is the case, it takes the additional className *active* to reflect the change in color:

```jsx
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 2500;

function Slideshow() {
  const [index, setIndex] = React.useState(0);

  React.useEffect(() => {
    setTimeout(
      () =>
        setIndex((prevIndex) =>
          prevIndex === colors.length - 1 ? 0 : prevIndex + 1
        ),
      delay
    );

    return () => {};
  }, [index]);

  return (
    <div className="slideshow">
      <div
        className="slideshowSlider"
        style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
      >
        {colors.map((backgroundColor, index) => (
          <div
            className="slide"
            key={index}
            style={{ backgroundColor }}
          ></div>
        ))}
      </div>

      <div className="slideshowDots">
        {colors.map((_, idx) => (
          <div
            key={idx}
            className={`slideshowDot${index === idx ? " active" : ""}`}
          ></div>
        ))}
      </div>
    </div>
  );
}

```

Now let's add styles corresponding to the className *active*:

```css
/* Slideshow */

.slideshow {
  margin: 0 auto;
  overflow: hidden;
  max-width: 500px;
}

.slideshowSlider {
  white-space: nowrap;
  transition: ease 1000ms;
}

.slide {
  display: inline-block;

  height: 400px;
  width: 100%;
  border-radius: 40px;
}

/* Buttons */

.slideshowDots {
  text-align: center;
}

.slideshowDot {
  display: inline-block;
  height: 20px;
  width: 20px;
  border-radius: 50%;

  cursor: pointer;
  margin: 15px 7px 0px;

  background-color: #c4c4c4;
}

.slideshowDot.active {
  background-color: #6a0dad;
}
```

Our buttons now reflect the changes in the slideshow:

<figure>
<img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605390479/blog/automated-slideshow-react/active_buttons_wg3tkl.gif"/>
<figcaption>Buttons do now reflect the changes
</figcaption>
</figure>

Now let's make them clickable, so when we click on the first dot we display the blue container, if we click on the second dot we display the green contain and if we click on the third dot we display the yellow container.

To achieve this, we change the index of the slide to be the same as the index of the button:

```jsx
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 2500;

function Slideshow() {
  const [index, setIndex] = React.useState(0);

  React.useEffect(() => {
    setTimeout(
      () =>
        setIndex((prevIndex) =>
          prevIndex === colors.length - 1 ? 0 : prevIndex + 1
        ),
      delay
    );

    return () => {};
  }, [index]);

  return (
    <div className="slideshow">
      <div
        className="slideshowSlider"
        style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
      >
        {colors.map((backgroundColor, index) => (
          <div
            className="slide"
            key={index}
            style={{ backgroundColor }}
          ></div>
        ))}
      </div>

      <div className="slideshowDots">
        {colors.map((_, idx) => (
          <div
            key={idx}
            className={`slideshowDot${index === idx ? " active" : ""}`}
            onClick={() => {
              setIndex(idx);
            }}
          ></div>
        ))}
      </div>
    </div>
  );
}

```

It works, however, because we didn't clear our `setTimeout`, by clicking multiple times on the dots we've distorted the value of the timer:

<figure>
<img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605391811/blog/automated-slideshow-react/no_cleared_out_timeout_yet_iclexc.gif"/>
<figcaption>The timer value is not cleared
</figcaption>
</figure>

To avoid such scenario, we'll clear our `setTimeout` by using the [`clearTimeout`](https://www.w3schools.com/jsref/met_win_settimeout.asp) method. The ID value returned by `setTimeout()` is used as the parameter for the `clearTimeout()`.

We will store it in a variable and use `clearTimeout()` to start the timer from 0, to avoid the scenario in the GIF above.

To store the variable, we use [useRef](https://reactjs.org/docs/hooks-reference.html#useref) to create an object whose value is accessed with the object's `current` property:

```jsx
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 2500;

function Slideshow() {
  const [index, setIndex] = React.useState(0);
  const timeoutRef = React.useRef(null);

  React.useEffect(() => {
    timeoutRef.current = setTimeout(
      () =>
        setIndex((prevIndex) =>
          prevIndex === colors.length - 1 ? 0 : prevIndex + 1
        ),
      delay
    );

    return () => {};
  }, [index]);

  return (
    <div className="slideshow">
      <div
        className="slideshowSlider"
        style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
      >
        {colors.map((backgroundColor, index) => (
          <div
            className="slide"
            key={index}
            style={{ backgroundColor }}
          ></div>
        ))}
      </div>

      <div className="slideshowDots">
        {colors.map((_, idx) => (
          <div
            key={idx}
            className={`slideshowDot${index === idx ? " active" : ""}`}
            onClick={() => {
              setIndex(idx);
            }}
          ></div>
        ))}
      </div>
    </div>
  );
}

```

Now we'll create a function *resetTimeout* to `clearTimeout`, and it'll be called every time the index of the slide changes.

To cleanup after the effect (when the component gets destroyed), we call the *resetTimeout* function to clear the timeout:

```jsx
const colors = ["#0088FE", "#00C49F", "#FFBB28"];
const delay = 2500;

function Slideshow() {
  const [index, setIndex] = React.useState(0);
  const timeoutRef = React.useRef(null);

  function resetTimeout() {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
  }

  React.useEffect(() => {
    resetTimeout();
    timeoutRef.current = setTimeout(
      () =>
        setIndex((prevIndex) =>
          prevIndex === colors.length - 1 ? 0 : prevIndex + 1
        ),
      delay
    );

    return () => {
      resetTimeout();
    };
  }, [index]);

  return (
    <div className="slideshow">
      <div
        className="slideshowSlider"
        style={{ transform: `translate3d(${-index * 100}%, 0, 0)` }}
      >
        {colors.map((backgroundColor, index) => (
          <div
            className="slide"
            key={index}
            style={{ backgroundColor }}
          ></div>
        ))}
      </div>

      <div className="slideshowDots">
        {colors.map((_, idx) => (
          <div
            key={idx}
            className={`slideshowDot${index === idx ? " active" : ""}`}
            onClick={() => {
              setIndex(idx);
            }}
          ></div>
        ))}
      </div>
    </div>
  );
}

```

Now we can click on the dots as much as we want, the slideshow will still work perfectly fine:

<figure>
<img loading="lazy"
src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1605392354/blog/automated-slideshow-react/playing_with_dots_yddfuf.gif"/>
<figcaption>The timer value is now cleared
</figcaption>
</figure>

---

# Learn more React

Learning by doing is the best thing. Here are more Tinloof React tutorials:

- [How to create React Notifications/Toasts](https://tinloof.com/blog/how-to-create-react-notifications-with-0-dependencies/)
- [How to create an accessible React modal](https://tinloof.com/blog/how-to-create-an-accessible-react-modal/)

>**Note**: Tinloof is a Berlin-based product studio. Get in touch if you'd like us to help you build slick and fast web applications.