---
title: "Building a Customizable Image Slider in React Using Hooks, SCSS, and TypeScript"
description: "This article will guide you through the process of creating a React slider component using Hooks, SCSS, and TypeScript. By the end of this tutorial, you will have a functional and customizable slider that can be easily integrated into your project."
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/blog/post/building-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript
---

# Building a Customizable Image Slider in React Using Hooks, SCSS, and TypeScript

## Introduction

In this tutorial, we will be building a customizable image slider in React using hooks, SCSS, and TypeScript. An image slider is a common UI element used in web applications to display a set of images that can be scrolled or navigated through. With React, building an image slider becomes easier and more modular.

Hooks are a new addition to React 16.8 that allows you to use state and other React features without writing a class. Hooks enable the creation of reusable and composable logic for React components.

SCSS is a preprocessor for CSS, which makes it easier to write and maintain large stylesheets. It allows you to use variables, mixins, and functions to create more modular and maintainable styles.

TypeScript is a typed superset of JavaScript that adds type annotations and other features to make it easier to catch errors and refactor code. TypeScript improves the developer experience and makes code easier to understand and maintain.

By the end of this tutorial, you will have a basic understanding of how to create a customizable image slider in React using hooks, SCSS, and TypeScript. You will also learn how to create a reusable component that can be easily customized and styled to fit your needs.

## Prerequisites

Before starting to build the customizable image slider in React, you should have a basic understanding of React, JavaScript, and CSS. You should be familiar with React hooks, including useState and useEffect, and have a working knowledge of TypeScript. Additionally, you should have Node.js and npm (Node Package Manager) installed on your machine, as we will be using them to set up the project and manage its dependencies.

If you are new to React or TypeScript, it is recommended that you first complete some beginner-level tutorials to familiarize yourself with the fundamentals of the technologies. Once you have a solid understanding of the basics, you will be better equipped to follow along with this tutorial and build your own customizable image slider.

## Getting Started

n this section, we will guide you through setting up the project. To start with, we will create a new React project using vite. Vite is a fast and efficient build tool that enables you to develop your React application quickly. It is built on top of Rollup and ESBuild, which makes it a lightweight and user-friendly tool.

To create a new project, open up your terminal and run the following command:

```shell
npx create-vite react-hooks-slider --template react-ts
```

This command will create a new React project with the name `react-hooks-slider` using the `react-ts` template, which includes TypeScript support. Once the project is created, navigate into the project directory using the `cd` command:

```shell
cd react-hooks-slider
```

This will create a new React project using TypeScript. After that, we will install the dependencie for SCSS. To do this, run the following command:

```shell
npm install -D sass
```

Now that we have created our project, we can begin setting up our slider component using React, Hooks, SCSS, and TypeScript. In the next section, we will start by creating a new React component for our slider.

## Creating the Slider Component

The first step in building our customizable image slider is to create a React component that will render our slider. We'll call this component `Slider`.

To create the `Slider` component, we'll first need to import React and the necessary hooks from React, including `useState`, `useEffect`, and `useRef`. We'll also need to import any external libraries or components that we'll be using in our slider.

Next, we'll define our Slider function component and set up its initial state using the `useState` hook. We'll keep track of the current index of the active slide and the previous and next slide indexes. We'll also create a `ref` using the `useRef` hook that will allow us to access the container element of our slider.

Then, we'll set up the `useEffect` hook to update the slider's state and animate the slides whenever the current index changes. This hook will listen for changes to the current index and adjust the previous and next slide indexes accordingly. It will also update the position of the slider using CSS transforms to animate the slides.

### Creating the Component Structure

In this section, we will create the basic structure of our slider component. We will start by creating a new folder called `components` inside the `src` folder. Inside the `components` folder, we will create a new directory called `Slider` and `Icons`. The `Slider` directory will contain the files for our slider component, and the `Icons` directory will contain the SVG as React components.

At the end of this section, our project structure will look like this:

```shell
react-hooks-slider
├── dist
├── node_modules
├── public
├── src
│   ├── components
│   │   ├── Icons
│   │   │   ├── RightArrowIcon.test.tsx
│   │   │   └── RightArrowIcon.tsx
│   │   └── Slider
│   │       ├── index.tsx
│   │       ├── Slide.test.tsx
│   │       ├── Slide.tsx
│   │       ├── Slider.test.tsx
│   │       ├── Slider.tsx
│   │       ├── SliderControl.test.tsx
│   │       ├── SliderControl.tsx
│   │       └── style.scss
│   ├── data
│   │   └── slider.data.json
│   ├── App.tsx
│   ├── main.tsx
│   ├── style.scss
│   └── vite-env.d.ts
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
```

Now that we have created our project structure, we can start creating our slider component. To do this, at first, we will create a new file called `RightArrowIcon.tsx` inside the `Icons` directory. This file will contain the SVG for the right arrow icon that we will use in our slider component.

```tsx title="src/components/Icons/RightArrowIcon.tsx"

// type

  fill?: string;
  size?: string;
  [x: string]: any;
};

const index = (props: Props) => {
  const {fill = 'currentColor', size = '24', ...otherProps} = props;

  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width={size}
      height={size}
      viewBox="0 0 24 24"
      fill={fill}
      {...otherProps}
    >
      <path d="m11.293 17.293 1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z"></path>
    </svg>
  );
};

```

In the above code, we have created a new React component called `RightArrowIcon`. This component will render the SVG for the right arrow icon. We have also added the `fill` and `size` props to the component, which will allow us to customize the color and size of the icon.

Next, we will create a new file called `Slide.tsx` inside the `Slider` directory. This file will contain the main slider component. We will start by importing the `RightArrowIcon` component that we created in the previous step.

```tsx title="src/components/Slider/Slide.tsx"
// icons

// types

  name: string;
};

  name: string;
  url: string;
};

  index: number;
  src: string;
  headline: string;
  direction?: string;
  tags?: Tag[];
  links?: Link[];
};

  slide: SlideData;
  index: number;
  current: number;
  handleSlideClick: (e: MouseEvent) => void;
};

const Slide = ({slide, index, current, handleSlideClick}: SlideProps) => {
  const slideRef = useRef(null);

  const handleMouseMove = (e: MouseEvent) => {
    const el = slideRef.current;
    const r = el!.getBoundingClientRect();

    el!.style.setProperty(
      '--x',
      (e.clientX - (r.left + Math.floor(r.width / 2))).toString(),
    );
    el!.style.setProperty(
      '--y',
      (e.clientY - (r.top + Math.floor(r.height / 2))).toString(),
    );
  };

  const handleMouseLeave = (e: MouseEvent) => {
    const el = slideRef.current;
    if (el) {
      el.style.setProperty('--x', '0');
      el.style.setProperty('--y', '0');
    }
  };

  useEffect(() => {
    const el = slideRef.current!.querySelector('img');
    el!.style.opacity = '1';
  }, []);

  const {src, headline, direction, tags, links} = slide;
  let classNames = 'slide';

  if (current === index) classNames += ' slide--current';
  else if (current - 1 === index) classNames += ' slide--previous';
  else if (current + 1 === index) classNames += ' slide--next';

  return (
    <div
      ref={slideRef}
      className={classNames}
      onClick={handleSlideClick}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      data-index={index}
    >
      <div className={'slide__image-wrapper'}>
        <img className={'slide__image'} alt={headline} src={src} />
      </div>

      <div className={'slide__overlay'}>
        <div className={'slide__content'}>
          <h2 className={'slide__content--headline'}>{headline}</h2>

          {direction && <p className={''}>{direction}</p>}

          {tags && (
            <div className={'slide__content--tag-wrapper'}>
              Tags:{' '}
              {tags.map((tag: Tag, index: number) => (
                <span key={index} className={'slide__content--tag'}>
                  {tag?.name}
                </span>
              ))}
            </div>
          )}

          <div className={'slide__content--button-wrapper'}>
            {links?.map((link: Link, index: number) => (
              <a
                key={index}
                className={'slide__content--button'}
                href={link?.url}
                target={'__blank'}
              >
                {link?.name}{' '}
                <span className={'slide__content--button__icon'}>

                </span>
              </a>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
};

```

In the above code, we have created a new React component called `Slide`. This component will render the main slider component. We have also added the `slide`, `index`, `current`, and `handleSlideClick` props to the component, which will allow us to pass the data for the slider and handle the click event on the slider.

Next, we will create a new file called `SliderControl.tsx` inside the `Slider` directory. This file will contain the main slider component. We will start by importing the `Slide` component that we created in the previous step.

```tsx title="src/components/Slider/SliderControl.tsx"

// types

  type: string;
  title: string;
  handleClick: (e: MouseEvent) => void;
};

const SliderControl = ({type, title, handleClick}: SliderControlProps) => {
  return (
    <button className={`btn btn--${type}`} title={title} onClick={handleClick}>
      <svg className={'icon'} viewBox={'0 0 24 24'}>
        <path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />
      </svg>
    </button>
  );
};

```

In the above code, we have created a new React component called `SliderControl`. This component will render the slider control buttons. We have also added the `type`, `title`, and `handleClick` props to the component, which will allow us to pass the data for the slider control buttons and handle the click event on the slider control buttons.

Next, we will create a new file called `Slider.tsx` inside the `Slider` directory. This file will contain the main slider component. We will start by importing the `Slide` and `SliderControl` components that we created in the previous steps.

```tsx title="src/components/Slider/Slider.tsx"

  slides: SlideData[];
  heading: string;
};

const Slider = ({slides, heading}: SliderProps) => {
  const [current, setCurrent] = useState(0);
  const headingId = `slider-heading__${heading
    .replace(/\s+/g, '-')
    .toLowerCase()}`;
  const wrapperTransform = {
    transform: `translateX(-${current * (100 / slides.length)}%)`,
  };

  const handlePreviousClick = () => {
    const previous = current - 1;

    setCurrent(previous < 0 ? slides.length - 1 : previous);
  };

  const handleNextClick = () => {
    const next = current + 1;

    setCurrent(next === slides.length ? 0 : next);
  };

  const handleSlideClick = (e: MouseEvent) => {
    const index = e.currentTarget?.getAttribute('data-index');
    if (index && current !== +index) {
      setCurrent(+index);
    }
  };

  return (
    <>
      <div className={'slider'} aria-labelledby={headingId}>
        <div className={'slider__wrapper'} style={wrapperTransform}>
          <h3 id={headingId} className={'visuallyhidden'}>
            {heading}
          </h3>

          {slides.map((slide, index: number) => (

          ))}
        </div>
      </div>

      <div className={'slider__controls'}>

      </div>
    </>
  );
};

```

In the above code, we have created a new React component called `Slider`. This component will render the main slider component. We have also added the `slides` and `heading` props to the component, which will allow us to pass the data for the slider.

Next, we will create a new file called `index.tsx` inside the `Slider` directory. This file will contain the main slider component. We will start by importing the `Slider` component that we created in the previous step.

```tsx title="src/components/Slider/index.tsx"

const SliderComponent = ({
  slides,
  heading,
}: {
  slides: SlideData[];
  heading: string;
}) => {
  return (
    <div>

    </div>
  );
};

```

In the above code, we have created a new React component called `SliderComponent`. This component will render the main slider component. We have also added the `slides` and `heading` props to the component, which will allow us to pass the data for the slider.

Next, we will create a new file called `style.scss` inside the `Slider` directory. This file will contain the main slider component styles.

```scss title="src/components/Slider/style.scss"
:root {
  --color-primary: #6b7a8f;
  --color-secondary: #101118;
  --color-accent: #1d1f2f;
  --color-focus: #6d64f7;
  --base-duration: 600ms;
  --base-ease: cubic-bezier(0.25, 0.46, 0.45, 0.84);
}

.visuallyhidden {
  clip: rect(0.0625rem, 0.0625rem, 0.0625rem, 0.0625rem);
  height: 0.0625rem;
  overflow: hidden;
  position: absolute !important;
  white-space: nowrap;
  width: 0.0625rem;
}

.icon {
  fill: var(--color-primary);
  width: 100%;
}

.btn {
  background-color: var(--color-primary);
  border: none;
  border-radius: 0.125rem;
  color: white;
  cursor: pointer;
  font-family: inherit;
  font-size: inherit;
  padding: 1rem 2.5rem 1.125rem;

  &:focus {
    outline-color: var(--color-focus);
    outline-offset: 0.125rem;
    outline-style: solid;
    outline-width: 0.1875rem;
  }

  &:active {
    transform: translateY(0.0625rem);
  }
}

.slider {
  --slide-size: 55vmin;
  --slide-margin: 4vmin;
  height: var(--slide-size);
  width: var(--slide-size);
  margin: 0 auto;
  position: relative;

  &__wrapper {
    display: flex;
    margin: 0 calc(var(--slide-margin) * -1);
    position: absolute;
    transition: transform var(--base-duration) cubic-bezier(0.25, 1, 0.35, 1);
  }

  &__controls {
    display: flex;
    justify-content: center;
    width: 100%;
    margin-top: 0.5rem;

    .btn {
      --size: 3rem;
      align-items: center;
      background-color: transparent;
      border: 0.188rem solid transparent;
      border-radius: 100%;
      display: flex;
      height: var(--size);
      width: var(--size);
      padding: 0;

      &:focus {
        border-color: var(--color-focus);
        outline: none;
      }

      &--previous {
        > * {
          transform: rotate(180deg);
        }
      }
    }
  }
}

.slide {
  align-items: center;
  color: white;
  display: flex;
  flex: 1;
  flex-direction: column;
  height: var(--slide-size);
  justify-content: center;
  margin: 0 var(--slide-margin);
  opacity: 0.25;
  position: relative;
  text-align: center;
  transition:
    opacity calc(var(--base-duration) / 2) var(--base-ease),
    transform calc(var(--base-duration) / 2) var(--base-ease);
  width: var(--slide-size);
  z-index: 1;

  &__image {
    --d: 20;
    height: 110%;
    -o-object-fit: cover;
    object-fit: cover;
    opacity: 0;
    pointer-events: none;
    transition:
      opacity var(--base-duration) var(--base-ease),
      transform var(--base-duration) var(--base-ease);
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    width: 110%;

    &-wrapper {
      background-color: var(--color-accent);
      border-radius: 1%;
      height: 100%;
      left: 0%;
      overflow: hidden;
      position: absolute;
      top: 0%;
      transition: transform calc(var(--base-duration) / 4) var(--base-ease);
      width: 100%;
    }
  }

  &__overlay {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    height: 100%;
    width: 100%;
    transition: 0.5s ease;
    background-color: #00000080;
    transition: transform calc(var(--base-duration) / 4) var(--base-ease);
  }

  &__content {
    --d: 60;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
    opacity: 0;
    padding: 1rem;
    position: relative;
    transition: transform var(--base-duration) var(--base-ease);
    visibility: hidden;

    &--headline {
      font-size: 3rem;
      font-weight: 600;
      position: relative;
      color: white;
    }

    &--tag {
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 0.3rem;

      &-wrapper {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.5rem;
        margin-top: 0.5rem;
        user-select: none;
      }
    }

    &--button {
      width: max-content;
      color: var(--first-color);
      font-size: var(--small-font-size);
      display: flex;
      align-items: center;
      column-gap: 0.25rem;

      &-wrapper {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.5rem;
        margin-top: 0.5rem;
        user-select: none;
      }

      &__icon {
        width: 1rem;
        height: 1rem;
        transition: 0.4s;

        svg {
          width: 1rem;
          height: 1rem;
        }
      }

      &:hover &__icon {
        transform: translateX(0.25rem);
      }
    }
  }

  &--previous {
    &:hover {
      opacity: 0.5;
      transform: translateX(2%);
    }
    cursor: w-resize;
  }

  &--current {
    --x: 0;
    --y: 0;
    --d: 50;
    opacity: 1;
    pointer-events: auto;
    -webkit-user-select: auto;
    -moz-user-select: auto;
    -ms-user-select: auto;
    user-select: auto;

    .slide {
      &__content {
        -webkit-animation: fade-in calc(var(--base-duration) / 2)
          var(--base-ease) forwards;
        animation: fade-in calc(var(--base-duration) / 2) var(--base-ease)
          forwards;
        visibility: visible;
      }
    }
  }

  &--next {
    &:hover {
      opacity: 0.5;
      transform: translateX(-2%);
    }
    cursor: e-resize;
  }
}

@media screen and (max-width: 568px) {
  .slider {
    --slide-size: 90vmin;
  }
}

@media (hover: hover) {
  .slide {
    &--current {
      &:hover {
        .slide {
          &__image {
            &-wrapper {
              transform: scale(1.025)
                translate(
                  calc(var(--x) / var(--d) * 0.063rem),
                  calc(var(--y) / var(--d) * 0.063rem)
                );
            }
          }

          &__overlay {
            transform: scale(1.025)
              translate(
                calc(var(--x) / var(--d) * 0.063rem),
                calc(var(--y) / var(--d) * 0.063rem)
              );
          }
        }
      }
      .slide {
        &__image {
          transform: translate(
            calc(var(--x) / var(--d) * 0.063rem),
            calc(var(--y) / var(--d) * 0.063rem)
          );
        }

        &__content {
          transform: translate(
            calc(var(--x) / var(--d) * -0.063rem),
            calc(var(--y) / var(--d) * -0.063rem)
          );
        }
      }
    }
  }
  .slide {
    &__overlay {
      transform: translate(
        calc(var(--x) / var(--d) * 0.063rem),
        calc(var(--y) / var(--d) * 0.063rem)
      );
    }
  }
}

@-webkit-keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
```

### Running the Slider Component

Now that we have the slider component, we need to add it at `App.tsx`:

```tsx title="App.tsx"
// Import the slider component

// Import the slide data type

// Import the slider data

const App = () => {
  return (
    <div className="app">

    </div>
  );
};

```

Now, we can run the command `npm run dev` to run the application.

```shell
npm run dev
```

After running the command, you will see the slider component in the home page:

![Slider component](/assets/blog/0041-building-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript/slider-component-demo.png)

### Testing the Slider Component

To start testing the slider component, we will need to install some dependencies. Run the following command:

```shell
npm install --save-dev @testing-library/jest-dom @testing-library/react @types/jest jest ts-jest
```

After installing the dependencies, we will add the `jest` at `package.json`:

```json:package.json
{
  ...,
  "jest": {
    "transform": {
      "^.+\\.tsx$": "ts-jest",
      "^.+\\.ts$": "ts-jest"
    },
    "testEnvironment": "jest-environment-jsdom"
  }
}
```

Now that we have the slider component, we can test it. We will use the slider component in the home page. Open the `src/components/Icons/RightArrowIcon.test.tsx` file and add the following code:

```tsx title="src/components/Icons/RightArrowIcon.test.tsx"

describe('RightArrowIcon', () => {
  test('renders', () => {
    const {container} = render();
    expect(container.firstChild).toBeInTheDocument();
  });

  test('has correct fill', () => {
    const {container} = render();
    expect(container.firstChild).toHaveAttribute('fill', 'red');
  });

  test('has correct size', () => {
    const {container} = render();
    expect(container.firstChild).toHaveAttribute('width', '24');
    expect(container.firstChild).toHaveAttribute('height', '24');
  });
});
```

Now, we will add the slider component to the home page. Open the `SliderControl.test.tsx` file and add the following code:

```tsx title="src/components/SliderControl.test.tsx"

describe('SliderControl component', () => {
  it('should render the correct title and type', () => {
    const handleClick = jest.fn();
    const {getByTitle} = render(

_Previous slide_
,
    );

    expect(getByTitle('Previous slide')).toBeInTheDocument();
    expect(getByTitle('Previous slide')).toHaveClass('btn--prev');
  });

  it('should call the handleClick function when clicked', () => {
    const handleClick = jest.fn();
    const {getByTitle} = render(

_Next slide_
,
    );

    fireEvent.click(getByTitle('Next slide'));
    expect(handleClick).toHaveBeenCalled();
  });
});
```

Now, we will add the slider component to the home page. Open the `Slide.test.tsx` file and add the following code:

```tsx title="src/components/Slide.test.tsx"

const slideData: SlideData = {
  index: 0,
  src: 'test-image.jpg',
  headline: 'Test Headline',
  direction: 'Test Direction',
  tags: [{name: 'Tag 1'}, {name: 'Tag 2'}],
  links: [
    {name: 'Link 1', url: 'http://test.com'},
    {name: 'Link 2', url: 'http://test2.com'},
  ],
};

describe('Slide component', () => {
  test('renders with slide data', () => {
    const {getByText, getByAltText} = render(
      ,
    );
    expect(getByAltText('Test Headline')).toBeInTheDocument();
    expect(getByText('Test Headline')).toBeInTheDocument();
    expect(getByText('Test Direction')).toBeInTheDocument();
    expect(getByText('Tags:')).toBeInTheDocument();
    expect(getByText('Tag 1')).toBeInTheDocument();
    expect(getByText('Tag 2')).toBeInTheDocument();
    expect(getByText('Link 1')).toHaveAttribute('href', 'http://test.com');
    expect(getByText('Link 2')).toHaveAttribute('href', 'http://test2.com');
  });

  test('adds slide--current class when current prop matches index prop', () => {
    const {container} = render(
      ,
    );
    expect(container.firstChild).toHaveClass('slide slide--current');
  });

  test('adds slide--next class when current prop is 1 less than index prop', () => {
    const {container} = render(
      ,
    );
    expect(container.firstChild).toHaveClass('slide slide--next');
  });

  test('adds slide--previous class when current prop is 1 more than index prop', () => {
    const {container} = render(
      ,
    );
    expect(container.firstChild).toHaveClass('slide slide--previous');
  });

  test('calls handleSlideClick function when clicked', () => {
    const mockHandleSlideClick = jest.fn();
    const {container} = render(
      ,
    );
    fireEvent.click(container.firstChild!);
    expect(mockHandleSlideClick).toHaveBeenCalled();
  });
});
```

Now, we will add the slider component to the home page. Open the `Slider.test.tsx` file and add the following code:

```tsx title="src/components/Slider.test.tsx"

describe('Slider', () => {
  const slides = [
    {
      src: 'img1.jpg',
      headline: 'Test Headline',
      direction: 'Test Direction',
      tags: [{name: 'Tag 1'}, {name: 'Tag 2'}],
      links: [
        {name: 'Link 1', url: 'http://test.com'},
        {name: 'Link 2', url: 'http://test2.com'},
      ],
    },
    {
      src: 'img2.jpg',
      headline: 'Test Headline 2',
      direction: 'Test Direction 2',
      tags: [{name: 'Tag 3'}, {name: 'Tag 4'}],
      links: [
        {name: 'Link 3', url: 'http://test3.com'},
        {name: 'Link 4', url: 'http://test4.com'},
      ],
    },
    {
      src: 'img3.jpg',
      headline: 'Test Headline 3',
      direction: 'Test Direction 3',
      tags: [{name: 'Tag 5'}, {name: 'Tag 6'}],
      links: [
        {name: 'Link 5', url: 'http://test5.com'},
        {name: 'Link 6', url: 'http://test6.com'},
      ],
    },
  ] as SlideData[];

  it('should render a slider with slides and controls', () => {
    const {getByText, getAllByRole} = render(
      ,
    );

    expect(getByText('Test Slider')).toBeInTheDocument();
    expect(getAllByRole('img').length).toBe(3);
    expect(getAllByRole('button').length).toBe(2);
  });

  it('should go to the next slide when clicking the "next" control', () => {
    const {getAllByRole} = render(
      ,
    );
    const nextButton = getAllByRole('button')[1];

    fireEvent.click(nextButton);
    expect(getAllByRole('img')[1]).toHaveAttribute('alt', 'Test Headline 2');
  });

  it('should go to the previous slide when clicking the "previous" control', () => {
    const {getAllByRole} = render(
      ,
    );
    const previousButton = getAllByRole('button')[0];

    fireEvent.click(previousButton);
    expect(getAllByRole('img')[2]).toHaveAttribute('alt', 'Test Headline 3');
  });

  it('should go to the clicked slide when clicking a slide', () => {
    const {getAllByRole} = render(
      ,
    );
    const slide = getAllByRole('img')[1];

    fireEvent.click(slide);
    expect(getAllByRole('img')[1]).toHaveAttribute('alt', 'Test Headline 2');
  });
});
```

After adding the tests, we will run the tests and see if they pass, but before that, we will add the `scripts` at `package.json` file:

```json title="package.json"
{
  ...,
  "scripts": {
    ...,
    "test": "jest",
    "test:coverage": "jest --coverage"
  },
  ...
}
```

Now, we will run the tests:

```shell
npm run test
```

## Conclusion

In this tutorial, we've walked through the process of building a customizable image slider in React using hooks, SCSS, and TypeScript. We started by setting up our project, creating a slider component, and implementing basic functionality to switch between images. We then added options to customize the behavior and appearance of the slider, such as Wloop, and navigation buttons.

Along the way, we learned about key React concepts such as props, state, and useEffect, as well as how to use hooks to manage component state and effects. We also saw how to use SCSS to write more expressive and reusable CSS styles, and how to leverage TypeScript to add type safety and improve code readability.

We hope this tutorial has been useful and informative, and that you now feel more comfortable building React applications with hooks, SCSS, and TypeScript. Happy coding!

## Source Code

You can find the source code for this tutorial on GitHub.

[SOURCE CODE](https://github.com/MKAbuMattar/react-hooks-slider)

## Live Demo

You can find the live demo for this tutorial on Vercel.

[LIVE DEMO](https://react-hooks-slider.vercel.app/)

## References

1.  [React Official Website - Hooks](https://reactjs.org/docs/hooks-intro.html)
2.  [TypeScript Official Website](https://www.typescriptlang.org/)
3.  [SCSS (Sass) Official Website](https://sass-lang.com/)
4.  [Vite Official Website](https://vitejs.dev/)
5.  [Jest Official Website - Testing Framework](https://jestjs.io/)
6.  [React Testing Library Documentation](https://testing-library.com/docs/react-testing-library/intro/)
7.  [MDN Web Docs - CSS Transforms](https://developer.mozilla.org/en-US/docs/Web/CSS/transform)
8.  [MDN Web Docs - SVG Tutorial](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial)
9.  [Using State Hook - React Docs](https://reactjs.org/docs/hooks-state.html)
10. [Using Effect Hook - React Docs](https://reactjs.org/docs/hooks-effect.html)
11. [Using Ref Hook - React Docs](https://reactjs.org/docs/hooks-reference.html#useref)
12. "Creating a Reusable Component in React" - (Example: Search for tutorials on Smashing Magazine or LogRocket.), [Link to a relevant tutorial]
13. "Styling React Components with SCSS" - (Example: Article from a frontend development blog.), [Link to a relevant article]
14. "Unit Testing React Components with Jest and React Testing Library" - (Example: Tutorial from DigitalOcean or a testing-focused blog.), [Link to a relevant tutorial]
15. [Node.js Official Website](https://nodejs.org/)
