Build a Slideshow Component with ReactJS
Tags: beginners, react, webdev, slideshow
Published: 2020-03-24 02:49:07

---
title: Build a Slideshow Component with ReactJS
published: true
description: Step-to-Step tutorial of how to build a slideshow component with ReactJS.
tags: #beginners #react #webdev #slideshow
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/8hl46hpxmioig532ilhg.png
---

Hey there! In this tutorial we are going to build a simple but cool Slideshow component with ReactJS.

Basically, our Slideshow will be compost of three images. One image for the current slide, one for the previous and another for the next one. It'll also have controllers for advancing to the next or previous slide and it'll also be able to change the slide automatically every period of time.

You can find the result of the Slideshow component [here](https://raymag.github.io/#/about).

### Notes

If you're going to follow along with this post, you'll need to be familiarized with [ReactJS](https://reactjs.org/) and [NodeJS](https://nodejs.org/en/).

## Table of Contents
1. [Setting up](#setting-up)
2. [Basic Component](#basic-component)
3. [Structure](#structure)
4. [Adding Style](#adding-style)
5. [Functionality](#functionality)
6. [Conclusion](#conclusion)

## Setting up

Alright, for this tutorial I'll use my [Personal Website](https://raymag.github.io/) as the base project. Naturally, you can use any ReactJS project as the base of the project.

I'm going to use [VS Code](https://code.visualstudio.com/) as IDE, but you can use any text editor or IDE you want.
With that said, this is how the directory tree of my website looks like.
![Website's Directory Tree](https://dev-to-uploads.s3.amazonaws.com/i/ir26ngc2juuue9rltsvd.png)

As you can see, in some way, all the code is inside the *src* directory. Consequently, all the components are inside the *components* folder. For organization purposes, I'm going to create a folder named **Slideshow**, with a empty *index.js* and *styles.css* file inside.
![Component Folder](https://dev-to-uploads.s3.amazonaws.com/i/3w8imgxd3svynp08yba6.png)

## Basic Component

First, we'll set up a simple component inside the *index.js* file.
```react
import React from 'react';

import './styles.css';

export default function Slideshow(){
    return (
        <section className="slideshow">
            Slideshow
        </section>
    )
}
```

Now I'll start the project with `yarn start` (you can use *npm* as well). I want to add the slideshow component right below the bio in this page.
![About Page](https://dev-to-uploads.s3.amazonaws.com/i/dgzc9zlooslwm8enexr9.png)

Now I'll import the Slideshow component into the web page and add it right below the bio.
```react
import React from 'react';
import Box from '../../components/Box';
import Slideshow from '../../components/Slideshow';

import './styles.css';

export default function About(){
    return (
        <>
        <Box color="#e5e5e5" bgColor="#421567" title="Carlos Magno" column >
            <div className="bio-container">
                <img src="./assets/dev.jpg" alt="dev" id="dev-pic" title="Magno"/>
                <div className="bio-text">
                    <p id="bio">
                    Introvert, Geek, Web Developer, Horde Player and CS Student...<br/>
                    Also known as <span className="italic">Raymag</span>, his hobbies are watching animations, learning new stuff, reading useful books and running.
                    </p><br/>
                    <p>prayk11@gmail.com</p>
                    <p>prayk11@outlook.com</p>
                </div>
            </div>
        </Box>
        <Box title="Gallery">
            <Slideshow />
        </Box>
        </>
    )
}
```

The *Box* component is just a component I made to help me make those big sections with different colors, so it's not important to us. Anyway, **this** is the result.
![Component Added](https://dev-to-uploads.s3.amazonaws.com/i/lra9f2gncngmttygs0hf.png)

## Structure

It's time to make the HTML structure of the Slideshow component. So let's edit the *index.js* file.
```react
(<section className="slideshow">
  <div className="slide-holder"></div>          
  <div className="slideshow-controller">
    <span>Previous</span>
    <span>Next</span>
   </div>
</section>)
```

Okay, we first have the section "slideshow" that hold the whole component. Inside this section we have two main elements, the div "slide-holder" (which will hold all the slides) and the div "slideshow-controller" (which will hold the buttons for going to the next or last slide). 

Now, I'm going to add three sections inside the "slide-holder". Each section represents a slide: the last, current and next one.
```react
(<section className="slideshow">
  <div className="slide-holder">
    <section className="slide previous-slide">
      <div className="slide-thumbnail"></div>
    </section>
    <section className="slide current-slide">
      <div className="slide-thumbnail"></div>
    </section>
    <section className="slide next-slide">
      <div className="slide-thumbnail"></div>
    </section>
  </div>          
  <div className="slideshow-controller">
    <span>Previous</span>
    <span>Next</span>
   </div>
</section>)
```

## Adding Style

It's time for add some styles for this component. So let's modify the *styles.css* file. Since CSS and UI is not the focus here, I'll not explain much about this part.
```css
section.slideshow{
    width: 80%;
}
section.slideshow div.slide-holder{
    display: flex;
    justify-content: space-evenly;
    align-items: center;
    flex-direction: row;
}
section.slideshow section.current-slide{
    width: 400px;
    height: 300px;
    margin: auto 10px;
}
section.slideshow section.previous-slide,
section.slideshow section.next-slide{
    width: 200px;
    height: 150px;
}
section.slideshow section.slide div.slide-thumbnail{
    width: 100%;
    height: 100%;
    background: #541d82;
    background-size: cover;
    background-position: center;
    border-radius: 6px;
    box-shadow: 5px 5px 6px #551d829c;
}
```

In short, all I've done is give all slides a purple background and made the current slide bigger. I'll also add some styles for the controllers as well.
```css
section.slideshow div.slideshow-controller{
    margin-top: 10px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
section.slideshow div.slideshow-controller span{
    padding: 10px;
    width: 100px;
    border: none;
    background: #541d82;
    color: #f5f5f5;
    cursor: pointer;
    text-align: center;
    box-shadow: 3px 3px 5px #551d8283;
    user-select: none;
}
section.slideshow div.slideshow-controller span:hover{
    color: #ffc200;
    text-shadow: 3px 3px 5px #ffc200;
}
section.slideshow div.slideshow-controller span:active{
    color: #541d82;
    text-shadow: 3px 3px 5px #541d82;
    background: #ffc200;
}
```
And that's the output.
![css](https://dev-to-uploads.s3.amazonaws.com/i/sxm78ztpwqv1um39ir2w.png)

## Functionality 

Now that we have added styles to it, we can finally start to code the functionalities of the component. For that purpose, we'll need a set of props and a few states. 

I'm going to use a array of *images* and a *interval* of time as props and since we're not using classes, we'll need to use **React Hooks** for controlling the states of the component. Which is why I'll import *useState* and *useEffect* methods from React. 

With that in mind, let's modify our *index.js* file.
```react
import React, {useState, useEffect} from 'react';

import './styles.css';

export default function Slideshow({images=[], interval=3000}){
    const [thumbnails, setThumnails] = useState([]);
    const [currentSlide, setCurrentSlide] = useState(0);
    const [currentSlideStyle, setCurrentSlideStyle] = useState({});

    return (
        <section className="slideshow">
            <div className="slide-holder">
                <section className="slide previous-slide">
                    <div className="slide-thumbnail"></div>
                </section>
                <section className="slide current-slide">
                    <div style={currentSlideStyle} className="slide-thumbnail"></div>
                </section>
                <section className="slide next-slide">
                    <div className="slide-thumbnail"></div>
                </section>
            </div>

            <div className="slideshow-controller">
                <span>Previous</span>
                <span>Next</span>
            </div>
        </section>
    )
}
```
As you can see, I've also created a few states.
- **thumbnails**: It holds the images array.
- **currentSlide**: It holds the index of the current slide in the *thumbnails* array.
- **currentSlideStyle**: We'll use it for setting the backgroung-image of the *"slide-thumbnail"* div.

Note that I've also assigned *currentSlideStyle* to the style property of the div inside the *"current-slide"*.

### Changing the background

Fine. Now we'll need to use the Effect Hook since we need to update the slides every time the component mounts or updates. So right below the state definitions we'll call our *useEffect* function passing our effect as a callback function.
```react
useEffect(()=>{
        setThumnails(images);
        setCurrentSlideStyle({
            backgroundImage: "url('"+images[currentSlide]+"')"
        });
    }, [images, currentSlide]);
```
This simply assigns the images *array* to the *thumbnails* state and also uses the *currentSlide* state (which holds the image index of the current slide) to set the *currentSlideStyle* state. Consequently, it sets the background-image of the "current slide" section.

Now I'll just pass a array of images and a interval to the Slideshow component in the page I want it to appear.
```react
<Slideshow
  interval={3000}
  images={[
    './assets/slideshow/img_1.jpg',
    './assets/slideshow/img_2.jpg',
    './assets/slideshow/img_3.jpg',
    './assets/slideshow/img_4.jpg',
    './assets/slideshow/img_5.jpg',
    './assets/slideshow/img_6.jpg'
  ]}
/>
```

This is how it is looking right now.
![output](https://dev-to-uploads.s3.amazonaws.com/i/afdfuofn9oil7inxfzzh.png)

### Making up the controllers

With that, we can now make the buttons start to working. For that, I'll just create two functions.

```react
import React, {useState, useEffect} from 'react';

import './styles.css';

export default function Slideshow({images=[], interval=3000}){
    const [thumbnails, setThumnails] = useState([]);
    const [currentSlide, setCurrentSlide] = useState(0);
    const [currentSlideStyle, setCurrentSlideStyle] = useState({});

    useEffect(()=>{
        setThumnails(images);
        setCurrentSlideStyle({
            backgroundImage: "url('"+images[currentSlide]+"')"
        });
    }, [images, currentSlide]);

    function previous(){
        if(currentSlide>0){
            setCurrentSlide(currentSlide-1);
        }else{
            setCurrentSlide(thumbnails.length-1);
        }
    }

    function next(){
        if(currentSlide === thumbnails.length-1){
            setCurrentSlide(0);
        }else{
            setCurrentSlide(currentSlide+1);
        }
    }

    return (
        <section className="slideshow">
            <div className="slide-holder">
                <section className="slide previous-slide">
                    <div className="slide-thumbnail"></div>
                </section>
                <section className="slide current-slide">
                    <div style={currentSlideStyle} className="slide-thumbnail"></div>
                </section>
                <section className="slide next-slide">
                    <div className="slide-thumbnail"></div>
                </section>
            </div>

            <div className="slideshow-controller">
                <span onClick={previous}>Previous</span>
                <span onClick={next}>Next</span>
            </div>
        </section>
    )
}
```

So the *previous* function gets the current slide index, finds out what is the previous one and assigns it to the *currentSlide* state.
In case the current slide index is 0 (the first one) it will then use the last position of the array.

Now the *next* function works in a similar way, it will find out what is the next slide position and assign it to *currentSlide* state. 
But in case the current slide index is the last one it will use the first position of the array.

Every time the *currentSlide* states changes, the component will update and the *useEffect* function will run again. In other words, it will change the background of the current slide. 

Note that I've also assigned the *previous* and *next* functions to the onClick event of each controller inside the "slide-controller" div.

### Automatically Advancing Slides

How to make the Slideshow component changes its slides every period of time? We just need to use the *setInterval* method inside the *useEffect*.
```react
useEffect(()=>{
        setThumnails(images);
        setCurrentSlideStyle({
            backgroundImage: "url('"+images[currentSlide]+"')"
        });
        
        const loop = setInterval(()=>{
            if(currentSlide === images.length-1){
                setCurrentSlide(0);
            }else{
                setCurrentSlide(currentSlide+1);
            }
        }, interval);
        return () => clearInterval(loop); 
    }, [images, currentSlide, interval]);
```
I've made a constant and assinged it to the setInterval function, passing a callback function and a interval (which is a prop). The callback function is basically the copy of the *next* function and it just sets the currentSlide to the next position.
With that, every period of time, the callback function will be executed and since it changes the *currentSlide* state, the component will updates and the *useEffect* will run again. Consequently, updating the background.
Using the return of the *useEffect*, we then clear the constant of the interval function to prevent it to going crazy and causing a stack overflow. 

If you run the component now, it will start to advance the slide every interval of time.

### Next and Previous Slides

What about the other two "slides" sections we've got in the "slide-holder"? Very well, it's time for us to make them work as well.

With the same logic of the current slide, we'll need a state for holding the styles of the next and previous slides.
We'll also need to update the *useEffect* function to make it updates the next and previous slides as well.
As well, we can't forget to add each style state to the "slide-thumbnail" in its "slide" section.

Since the code for that follows the same principle of the next and previous functions, I don't see necessity of explaining it further.
```react
import React, {useState, useEffect} from 'react';

import './styles.css';

export default function Slideshow({images=[], interval=3000}){
    const [thumbnails, setThumnails] = useState([]);
    const [previousSlideStyle, setPreviousSlideStyle] = useState({});
    const [currentSlide, setCurrentSlide] = useState(0);
    const [nextSlideStyle, setNextSlideStyle] = useState({});
    const [currentSlideStyle, setCurrentSlideStyle] = useState({});

    useEffect(()=>{
        setThumnails(images);
        setCurrentSlideStyle({
            backgroundImage: "url('"+images[currentSlide]+"')"
        });

        if(currentSlide>0){
            setPreviousSlideStyle({
                backgroundImage: "url('"+images[currentSlide-1]+"')"
            });
        }else{
            setPreviousSlideStyle({
                backgroundImage: "url('"+images[images.length-1]+"')"
            });
        }

        if(currentSlide === images.length-1){
            setNextSlideStyle({
                backgroundImage: "url('"+images[0]+"')"
            });
        }else{
            setNextSlideStyle({
                backgroundImage: "url('"+images[currentSlide+1]+"')"
            });
        } 
        
        const loop = setInterval(()=>{
            if(currentSlide === images.length-1){
                setCurrentSlide(0);
            }else{
                setCurrentSlide(currentSlide+1);
            }
        }, interval);
        return () => clearInterval(loop); 
    }, [images, currentSlide, interval]);

    function previous(){
        if(currentSlide>0){
            setCurrentSlide(currentSlide-1);
        }else{
            setCurrentSlide(thumbnails.length-1);
        }
    }

    function next(){
        if(currentSlide === thumbnails.length-1){
            setCurrentSlide(0);
        }else{
            setCurrentSlide(currentSlide+1);
        }
    }

    return (
        <section className="slideshow">
            <div className="slide-holder">
                <section className="slide previous-slide">
                    <div style={previousSlideStyle} className="slide-thumbnail"></div>
                </section>
                <section className="slide current-slide">
                    <div style={currentSlideStyle} className="slide-thumbnail"></div>
                </section>
                <section className="slide next-slide">
                    <div style={nextSlideStyle} className="slide-thumbnail"></div>
                </section>
            </div>

            <div className="slideshow-controller">
                <span onClick={previous}>Previous</span>
                <span onClick={next}>Next</span>
            </div>
        </section>
    )
}
```

## Conclusion

After all the hard work, we finally made it! This is the final result of the Slideshow component we've built here. 
![result](https://dev-to-uploads.s3.amazonaws.com/i/8hl46hpxmioig532ilhg.png)

If you've read until here, I'm really thankful for that.
If you have any suggestions, corrections and even doubts about something related to the post, feel free to leave a comment or contact me.