---
title: "Building a Customizable Image Slider in React Using Hooks, SCSS, and TypeScript"
lang: "en"
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/post/building-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript
---

![Blog post image for Building a Customizable Image Slider in React Using Hooks, SCSS, and TypeScript - 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.](/_astro/hero.CMtyRpHA_Z1eadI5.webp)

[Home](/)›[Blog](/blog)›[All Categories](/blog/categories)›[ReactJS](/blog/categories/reactjs)

Blog

[Next in ReactJSGet Started with Building ReactJS and Docker: A Complete Guide](/blog/post/get-started-with-building-reactjs-and-docker-a-complete-guide)

[ReactJS](/blog/categories/reactjs)[TypeScript](/blog/categories/typescript)[SCSS](/blog/categories/scss)[Frontend Development](/blog/categories/frontend-development)[UI Components](/blog/categories/ui-components)

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

[Mohammad Abu Mattar](/authors/mohammad-abu-mattar)Published: 17 Feb 202312 Mins read11 Mins listen

[Markdown for AI(opens in a new tab)](/post/building-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript/index.md "Open the plain-Markdown version of this page, for pasting into an AI tool")

TL;DR

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.

Series

[Advanced Frontend Techniques](/series/advanced-frontend-techniques)1/1

All posts in this series (1)

Blog1

1.  [Building a Customizable Image Slider in React Using Hooks, SCSS, and TypeScriptYou are here](/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

Contents

[{headline}](#headline)[{heading}](#heading)

## [Introduction](#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 allow 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](#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](#getting-started)

In 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:

Terminal window

```
1npx 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:

Terminal window

```
1cd react-hooks-slider
```

The `cd` command moves you into the new project. After that, we will install the dependency for SCSS. To do this, run the following command:

Terminal window

```
1npm 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](#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](#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 icons as React components.

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

Terminal window

```
1react-hooks-slider2├── dist3├── node_modules4├── public5├── src6│   ├── components7│   │   ├── Icons8│   │   │   ├── RightArrowIcon.test.tsx9│   │   │   └── RightArrowIcon.tsx10│   │   └── Slider11│   │       ├── index.tsx12│   │       ├── Slide.test.tsx13│   │       ├── Slide.tsx14│   │       ├── Slider.test.tsx15│   │       ├── Slider.tsx16│   │       ├── SliderControl.test.tsx17│   │       ├── SliderControl.tsx18│   │       └── style.scss19│   ├── data20│   │   └── slider.data.json21│   ├── App.tsx22│   ├── main.tsx23│   ├── style.scss24│   └── vite-env.d.ts25├── .gitignore26├── package-lock.json27├── package.json28├── README.md29├── tsconfig.json30├── tsconfig.node.json31└── 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.

src/components/Icons/RightArrowIcon.tsx

```
1import React from 'react';2
3// type4export type Props = {5  fill?: string;6  size?: string;7  [x: string]: any;8};9
10const index = (props: Props) => {11  const {fill = 'currentColor', size = '24', ...otherProps} = props;12
13  return (14    <svg15      xmlns="http://www.w3.org/2000/svg"16      width={size}17      height={size}18      viewBox="0 0 24 24"19      fill={fill}20      {...otherProps}21    >22      <path d="m11.293 17.293 1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z"></path>23    </svg>24  );25};26
27export default index;
```

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 component for a single slide. We will start by importing the `RightArrowIcon` component that we created in the previous step.

src/components/Slider/Slide.tsx

```
1// icons2import RightArrowIcon from '../Icons/RightArrowIcon';3import React, {useRef, useEffect, MouseEvent} from 'react';4
5// types6export type Tag = {7  name: string;8};9
10export type Link = {11  name: string;12  url: string;13};14
15export type SlideData = {16  index: number;17  src: string;18  headline: string;19  direction?: string;20  tags?: Tag[];21  links?: Link[];22};23
24export type SlideProps = {25  slide: SlideData;26  index: number;27  current: number;28  handleSlideClick: (e: MouseEvent<HTMLDivElement>) => void;29};30
31const Slide = ({slide, index, current, handleSlideClick}: SlideProps) => {32  const slideRef = useRef<HTMLDivElement>(null);33
34  const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {35    const el = slideRef.current;36    const r = el!.getBoundingClientRect();37
38    el!.style.setProperty(39      '--x',40      (e.clientX - (r.left + Math.floor(r.width / 2))).toString(),41    );42    el!.style.setProperty(43      '--y',44      (e.clientY - (r.top + Math.floor(r.height / 2))).toString(),45    );46  };47
48  const handleMouseLeave = (e: MouseEvent<HTMLDivElement>) => {49    const el = slideRef.current;50    if (el) {51      el.style.setProperty('--x', '0');52      el.style.setProperty('--y', '0');53    }54  };55
56  useEffect(() => {57    const el = slideRef.current!.querySelector('img');58    el!.style.opacity = '1';59  }, []);60
61  const {src, headline, direction, tags, links} = slide;62  let classNames = 'slide';63
64  if (current === index) classNames += ' slide--current';65  else if (current - 1 === index) classNames += ' slide--previous';66  else if (current + 1 === index) classNames += ' slide--next';67
68  return (69    <div70      ref={slideRef}71      className={classNames}72      onClick={handleSlideClick}73      onMouseMove={handleMouseMove}74      onMouseLeave={handleMouseLeave}75      data-index={index}76    >77      <div className={'slide__image-wrapper'}>78        <img className={'slide__image'} alt={headline} src={src} />79      </div>80
81      <div className={'slide__overlay'}>82        <div className={'slide__content'}>83          <h2 className={'slide__content--headline'}>{headline}</h2>84
85          {direction && <p className={''}>{direction}</p>}86
87          {tags && (88            <div className={'slide__content--tag-wrapper'}>89              Tags:{' '}90              {tags.map((tag: Tag, index: number) => (91                <span key={index} className={'slide__content--tag'}>92                  {tag?.name}93                </span>94              ))}95            </div>96          )}97
98          <div className={'slide__content--button-wrapper'}>99            {links?.map((link: Link, index: number) => (100              <a101                key={index}102                className={'slide__content--button'}103                href={link?.url}104                target={'__blank'}105              >106                {link?.name}{' '}107                <span className={'slide__content--button__icon'}>108                  <RightArrowIcon />109                </span>110              </a>111            ))}112          </div>113        </div>114      </div>115    </div>116  );117};118
119export default Slide;
```

In the above code, we have created a new React component called `Slide`. This component renders one slide. 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 previous and next buttons. We will start by importing the `Slide` component that we created in the previous step.

src/components/Slider/SliderControl.tsx

```
1import React, {MouseEvent} from 'react';2
3// types4export type SliderControlProps = {5  type: string;6  title: string;7  handleClick: (e: MouseEvent<HTMLButtonElement>) => void;8};9
10const SliderControl = ({type, title, handleClick}: SliderControlProps) => {11  return (12    <button className={`btn btn--${type}`} title={title} onClick={handleClick}>13      <svg className={'icon'} viewBox={'0 0 24 24'}>14        <path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />15      </svg>16    </button>17  );18};19
20export default SliderControl;
```

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.

src/components/Slider/Slider.tsx

```
1import Slide, {SlideData} from './Slide';2import SliderControl from './SliderControl';3import React, {useState, MouseEvent} from 'react';4
5export type SliderProps = {6  slides: SlideData[];7  heading: string;8};9
10const Slider = ({slides, heading}: SliderProps) => {11  const [current, setCurrent] = useState(0);12  const headingId = `slider-heading__${heading13    .replace(/\s+/g, '-')14    .toLowerCase()}`;15  const wrapperTransform = {16    transform: `translateX(-${current * (100 / slides.length)}%)`,17  };18
19  const handlePreviousClick = () => {20    const previous = current - 1;21
22    setCurrent(previous < 0 ? slides.length - 1 : previous);23  };24
25  const handleNextClick = () => {26    const next = current + 1;27
28    setCurrent(next === slides.length ? 0 : next);29  };30
31  const handleSlideClick = (e: MouseEvent<HTMLDivElement>) => {32    const index = e.currentTarget?.getAttribute('data-index');33    if (index && current !== +index) {34      setCurrent(+index);35    }36  };37
38  return (39    <>40      <div className={'slider'} aria-labelledby={headingId}>41        <div className={'slider__wrapper'} style={wrapperTransform}>42          <h3 id={headingId} className={'visuallyhidden'}>43            {heading}44          </h3>45
46          {slides.map((slide, index: number) => (47            <Slide48              key={index}49              index={index}50              slide={slide}51              current={current}52              handleSlideClick={handleSlideClick}53            />54          ))}55        </div>56      </div>57
58      <div className={'slider__controls'}>59        <SliderControl60          type={'previous'}61          title={'Go to previous slide'}62          handleClick={handlePreviousClick}63        />64
65        <SliderControl66          type={'next'}67          title={'Go to next slide'}68          handleClick={handleNextClick}69        />70      </div>71    </>72  );73};74
75export default Slider;
```

In the above code, we have created a new React component called `Slider`. This component renders the slider itself. 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 be the entry point for the slider. We will start by importing the `Slider` component that we created in the previous step.

src/components/Slider/index.tsx

```
1import {SlideData} from './Slide';2import Slider from './Slider';3import './style.scss';4import React from 'react';5
6const SliderComponent = ({7  slides,8  heading,9}: {10  slides: SlideData[];11  heading: string;12}) => {13  return (14    <div>15      <Slider slides={slides} heading={heading} />16    </div>17  );18};19
20export default SliderComponent;
```

In the above code, we have created a new React component called `SliderComponent`. This component wraps the slider and pulls in its styles. 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.

src/components/Slider/style.scss

```
1:root {2  --color-primary: #6b7a8f;3  --color-secondary: #101118;4  --color-accent: #1d1f2f;5  --color-focus: #6d64f7;6  --base-duration: 600ms;7  --base-ease: cubic-bezier(0.25, 0.46, 0.45, 0.84);8}9
10.visuallyhidden {11  clip: rect(0.0625rem, 0.0625rem, 0.0625rem, 0.0625rem);12  height: 0.0625rem;13  overflow: hidden;14  position: absolute !important;15  white-space: nowrap;16  width: 0.0625rem;17}18
19.icon {20  fill: var(--color-primary);21  width: 100%;22}23
24.btn {25  background-color: var(--color-primary);26  border: none;27  border-radius: 0.125rem;28  color: white;29  cursor: pointer;30  font-family: inherit;31  font-size: inherit;32  padding: 1rem 2.5rem 1.125rem;33
34  &:focus {35    outline-color: var(--color-focus);36    outline-offset: 0.125rem;37    outline-style: solid;38    outline-width: 0.1875rem;39  }40
41  &:active {42    transform: translateY(0.0625rem);43  }44}45
46.slider {47  --slide-size: 55vmin;48  --slide-margin: 4vmin;49  height: var(--slide-size);50  width: var(--slide-size);51  margin: 0 auto;52  position: relative;53
54  &__wrapper {55    display: flex;56    margin: 0 calc(var(--slide-margin) * -1);57    position: absolute;58    transition: transform var(--base-duration) cubic-bezier(0.25, 1, 0.35, 1);59  }60
61  &__controls {62    display: flex;63    justify-content: center;64    width: 100%;65    margin-top: 0.5rem;66
67    .btn {68      --size: 3rem;69      align-items: center;70      background-color: transparent;71      border: 0.188rem solid transparent;72      border-radius: 100%;73      display: flex;74      height: var(--size);75      width: var(--size);76      padding: 0;77
78      &:focus {79        border-color: var(--color-focus);80        outline: none;81      }82
83      &--previous {84        > * {85          transform: rotate(180deg);86        }87      }88    }89  }90}91
92.slide {93  align-items: center;94  color: white;95  display: flex;96  flex: 1;97  flex-direction: column;98  height: var(--slide-size);99  justify-content: center;100  margin: 0 var(--slide-margin);101  opacity: 0.25;102  position: relative;103  text-align: center;104  transition:105    opacity calc(var(--base-duration) / 2) var(--base-ease),106    transform calc(var(--base-duration) / 2) var(--base-ease);107  width: var(--slide-size);108  z-index: 1;109
110  &__image {111    --d: 20;112    height: 110%;113    -o-object-fit: cover;114    object-fit: cover;115    opacity: 0;116    pointer-events: none;117    transition:118      opacity var(--base-duration) var(--base-ease),119      transform var(--base-duration) var(--base-ease);120    -webkit-user-select: none;121    -moz-user-select: none;122    -ms-user-select: none;123    user-select: none;124    width: 110%;125
126    &-wrapper {127      background-color: var(--color-accent);128      border-radius: 1%;129      height: 100%;130      left: 0%;131      overflow: hidden;132      position: absolute;133      top: 0%;134      transition: transform calc(var(--base-duration) / 4) var(--base-ease);135      width: 100%;136    }137  }138
139  &__overlay {140    position: absolute;141    top: 0;142    bottom: 0;143    left: 0;144    right: 0;145    height: 100%;146    width: 100%;147    transition: 0.5s ease;148    background-color: #00000080;149    transition: transform calc(var(--base-duration) / 4) var(--base-ease);150  }151
152  &__content {153    --d: 60;154    display: flex;155    flex-direction: column;156    align-items: center;157    gap: 1rem;158    opacity: 0;159    padding: 1rem;160    position: relative;161    transition: transform var(--base-duration) var(--base-ease);162    visibility: hidden;163
164    &--headline {165      font-size: 3rem;166      font-weight: 600;167      position: relative;168      color: white;169    }170
171    &--tag {172      display: flex;173      justify-content: center;174      align-items: center;175      gap: 0.3rem;176
177      &-wrapper {178        display: flex;179        flex-wrap: wrap;180        align-items: center;181        gap: 0.5rem;182        margin-top: 0.5rem;183        user-select: none;184      }185    }186
187    &--button {188      width: max-content;189      color: var(--first-color);190      font-size: var(--small-font-size);191      display: flex;192      align-items: center;193      column-gap: 0.25rem;194
195      &-wrapper {196        display: flex;197        flex-wrap: wrap;198        align-items: center;199        gap: 0.5rem;200        margin-top: 0.5rem;201        user-select: none;202      }203
204      &__icon {205        width: 1rem;206        height: 1rem;207        transition: 0.4s;208
209        svg {210          width: 1rem;211          height: 1rem;212        }213      }214
215      &:hover &__icon {216        transform: translateX(0.25rem);217      }218    }219  }220
221  &--previous {222    &:hover {223      opacity: 0.5;224      transform: translateX(2%);225    }226    cursor: w-resize;227  }228
229  &--current {230    --x: 0;231    --y: 0;232    --d: 50;233    opacity: 1;234    pointer-events: auto;235    -webkit-user-select: auto;236    -moz-user-select: auto;237    -ms-user-select: auto;238    user-select: auto;239
240    .slide {241      &__content {242        -webkit-animation: fade-in calc(var(--base-duration) / 2)243          var(--base-ease) forwards;244        animation: fade-in calc(var(--base-duration) / 2) var(--base-ease)245          forwards;246        visibility: visible;247      }248    }249  }250
251  &--next {252    &:hover {253      opacity: 0.5;254      transform: translateX(-2%);255    }256    cursor: e-resize;257  }258}259
260@media screen and (max-width: 568px) {261  .slider {262    --slide-size: 90vmin;263  }264}265
266@media (hover: hover) {267  .slide {268    &--current {269      &:hover {270        .slide {271          &__image {272            &-wrapper {273              transform: scale(1.025)274                translate(275                  calc(var(--x) / var(--d) * 0.063rem),276                  calc(var(--y) / var(--d) * 0.063rem)277                );278            }279          }280
281          &__overlay {282            transform: scale(1.025)283              translate(284                calc(var(--x) / var(--d) * 0.063rem),285                calc(var(--y) / var(--d) * 0.063rem)286              );287          }288        }289      }290      .slide {291        &__image {292          transform: translate(293            calc(var(--x) / var(--d) * 0.063rem),294            calc(var(--y) / var(--d) * 0.063rem)295          );296        }297
298        &__content {299          transform: translate(300            calc(var(--x) / var(--d) * -0.063rem),301            calc(var(--y) / var(--d) * -0.063rem)302          );303        }304      }305    }306  }307  .slide {308    &__overlay {309      transform: translate(310        calc(var(--x) / var(--d) * 0.063rem),311        calc(var(--y) / var(--d) * 0.063rem)312      );313    }314  }315}316
317@-webkit-keyframes fade-in {318  from {319    opacity: 0;320  }321  to {322    opacity: 1;323  }324}325
326@keyframes fade-in {327  from {328    opacity: 0;329  }330  to {331    opacity: 1;332  }333}
```

### [Running the slider component](#running-the-slider-component)

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

App.tsx

```
1// Import the slider component2import Slider from './components/Slider';3// Import the slide data type4import {SlideData} from './components/Slider/Slide';5// Import the slider data6import sliderData from './data/slider.data.json';7import './style.scss';8import React from 'react';9
10const App = () => {11  return (12    <div className="app">13      <Slider14        slides={sliderData.slides as SlideData[]}15        heading={sliderData.heading}16      />17    </div>18  );19};20
21export default App;
```

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

Terminal window

```
1npm run dev
```

After running the command, you will see the slider component on 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](#testing-the-slider-component)

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

Terminal window

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

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

package.json

```
1{2  ...,3  "jest": {4    "transform": {5      "^.+\\.tsx$": "ts-jest",6      "^.+\\.ts$": "ts-jest"7    },8    "testEnvironment": "jest-environment-jsdom"9  }10}
```

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

src/components/Icons/RightArrowIcon.test.tsx

```
1import RightArrowIcon from './RightArrowIcon';2import '@testing-library/jest-dom';3import {render} from '@testing-library/react';4import React from 'react';5
6describe('RightArrowIcon', () => {7  test('renders', () => {8    const {container} = render(<RightArrowIcon />);9    expect(container.firstChild).toBeInTheDocument();10  });11
12  test('has correct fill', () => {13    const {container} = render(<RightArrowIcon fill="red" />);14    expect(container.firstChild).toHaveAttribute('fill', 'red');15  });16
17  test('has correct size', () => {18    const {container} = render(<RightArrowIcon size="24" />);19    expect(container.firstChild).toHaveAttribute('width', '24');20    expect(container.firstChild).toHaveAttribute('height', '24');21  });22});
```

Next, open the `SliderControl.test.tsx` file and add the following code:

src/components/SliderControl.test.tsx

```
1import SliderControl from './SliderControl';2import '@testing-library/jest-dom';3import {render, fireEvent} from '@testing-library/react';4import React from 'react';5
6describe('SliderControl component', () => {7  it('should render the correct title and type', () => {8    const handleClick = jest.fn();9    const {getByTitle} = render(10      <SliderControl11        type="prev"12        title="Previous slide"13        handleClick={handleClick}14      />,15    );16
17    expect(getByTitle('Previous slide')).toBeInTheDocument();18    expect(getByTitle('Previous slide')).toHaveClass('btn--prev');19  });20
21  it('should call the handleClick function when clicked', () => {22    const handleClick = jest.fn();23    const {getByTitle} = render(24      <SliderControl25        type="next"26        title="Next slide"27        handleClick={handleClick}28      />,29    );30
31    fireEvent.click(getByTitle('Next slide'));32    expect(handleClick).toHaveBeenCalled();33  });34});
```

Next, open the `Slide.test.tsx` file and add the following code:

src/components/Slide.test.tsx

```
1import Slide, {SlideData} from './Slide';2import '@testing-library/jest-dom';3import {render, fireEvent} from '@testing-library/react';4import React from 'react';5
6const slideData: SlideData = {7  index: 0,8  src: 'test-image.jpg',9  headline: 'Test Headline',10  direction: 'Test Direction',11  tags: [{name: 'Tag 1'}, {name: 'Tag 2'}],12  links: [13    {name: 'Link 1', url: 'http://test.com'},14    {name: 'Link 2', url: 'http://test2.com'},15  ],16};17
18describe('Slide component', () => {19  test('renders with slide data', () => {20    const {getByText, getByAltText} = render(21      <Slide22        slide={slideData}23        index={0}24        current={0}25        handleSlideClick={jest.fn()}26      />,27    );28    expect(getByAltText('Test Headline')).toBeInTheDocument();29    expect(getByText('Test Headline')).toBeInTheDocument();30    expect(getByText('Test Direction')).toBeInTheDocument();31    expect(getByText('Tags:')).toBeInTheDocument();32    expect(getByText('Tag 1')).toBeInTheDocument();33    expect(getByText('Tag 2')).toBeInTheDocument();34    expect(getByText('Link 1')).toHaveAttribute('href', 'http://test.com');35    expect(getByText('Link 2')).toHaveAttribute('href', 'http://test2.com');36  });37
38  test('adds slide--current class when current prop matches index prop', () => {39    const {container} = render(40      <Slide41        slide={slideData}42        index={0}43        current={0}44        handleSlideClick={jest.fn()}45      />,46    );47    expect(container.firstChild).toHaveClass('slide slide--current');48  });49
50  test('adds slide--next class when current prop is 1 less than index prop', () => {51    const {container} = render(52      <Slide53        slide={slideData}54        index={1}55        current={0}56        handleSlideClick={jest.fn()}57      />,58    );59    expect(container.firstChild).toHaveClass('slide slide--next');60  });61
62  test('adds slide--previous class when current prop is 1 more than index prop', () => {63    const {container} = render(64      <Slide65        slide={slideData}66        index={0}67        current={1}68        handleSlideClick={jest.fn()}69      />,70    );71    expect(container.firstChild).toHaveClass('slide slide--previous');72  });73
74  test('calls handleSlideClick function when clicked', () => {75    const mockHandleSlideClick = jest.fn();76    const {container} = render(77      <Slide78        slide={slideData}79        index={0}80        current={0}81        handleSlideClick={mockHandleSlideClick}82      />,83    );84    fireEvent.click(container.firstChild!);85    expect(mockHandleSlideClick).toHaveBeenCalled();86  });87});
```

Next, open the `Slider.test.tsx` file and add the following code:

src/components/Slider.test.tsx

```
1import {SlideData} from './Slide';2import Slider from './Slider';3import '@testing-library/jest-dom';4import {render, fireEvent} from '@testing-library/react';5import React from 'react';6
7describe('Slider', () => {8  const slides = [9    {10      src: 'img1.jpg',11      headline: 'Test Headline',12      direction: 'Test Direction',13      tags: [{name: 'Tag 1'}, {name: 'Tag 2'}],14      links: [15        {name: 'Link 1', url: 'http://test.com'},16        {name: 'Link 2', url: 'http://test2.com'},17      ],18    },19    {20      src: 'img2.jpg',21      headline: 'Test Headline 2',22      direction: 'Test Direction 2',23      tags: [{name: 'Tag 3'}, {name: 'Tag 4'}],24      links: [25        {name: 'Link 3', url: 'http://test3.com'},26        {name: 'Link 4', url: 'http://test4.com'},27      ],28    },29    {30      src: 'img3.jpg',31      headline: 'Test Headline 3',32      direction: 'Test Direction 3',33      tags: [{name: 'Tag 5'}, {name: 'Tag 6'}],34      links: [35        {name: 'Link 5', url: 'http://test5.com'},36        {name: 'Link 6', url: 'http://test6.com'},37      ],38    },39  ] as SlideData[];40
41  it('should render a slider with slides and controls', () => {42    const {getByText, getAllByRole} = render(43      <Slider slides={slides} heading={'Test Slider'} />,44    );45
46    expect(getByText('Test Slider')).toBeInTheDocument();47    expect(getAllByRole('img').length).toBe(3);48    expect(getAllByRole('button').length).toBe(2);49  });50
51  it('should go to the next slide when clicking the "next" control', () => {52    const {getAllByRole} = render(53      <Slider slides={slides} heading={'Test Slider'} />,54    );55    const nextButton = getAllByRole('button')[1];56
57    fireEvent.click(nextButton);58    expect(getAllByRole('img')[1]).toHaveAttribute('alt', 'Test Headline 2');59  });60
61  it('should go to the previous slide when clicking the "previous" control', () => {62    const {getAllByRole} = render(63      <Slider slides={slides} heading={'Test Slider'} />,64    );65    const previousButton = getAllByRole('button')[0];66
67    fireEvent.click(previousButton);68    expect(getAllByRole('img')[2]).toHaveAttribute('alt', 'Test Headline 3');69  });70
71  it('should go to the clicked slide when clicking a slide', () => {72    const {getAllByRole} = render(73      <Slider slides={slides} heading={'Test Slider'} />,74    );75    const slide = getAllByRole('img')[1];76
77    fireEvent.click(slide);78    expect(getAllByRole('img')[1]).toHaveAttribute('alt', 'Test Headline 2');79  });80});
```

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

package.json

```
1{2  ...,3  "scripts": {4    ...,5    "test": "jest",6    "test:coverage": "jest --coverage"7  },8  ...9}
```

Now, we will run the tests:

Terminal window

```
1npm run test
```

## [Conclusion](#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 looping, 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 use TypeScript to add type safety and improve code readability.

With this, you should feel more comfortable building React applications with hooks, SCSS, and TypeScript.

## [Source Code](#source-code)

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

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

## [Live Demo](#live-demo)

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

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

## [References](#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.  [Node.js Official Website](https://nodejs.org/)

Was this useful?

## Tags

[#React Hooks](/blog/tags/react-hooks)[#TypeScript](/blog/tags/typescript)[#SCSS](/blog/tags/scss)[#Image Slider](/blog/tags/image-slider)[#Customizable Components](/blog/tags/customizable-components)[#Frontend Tutorial](/blog/tags/frontend-tutorial)[#UI Development](/blog/tags/ui-development)[#Vite](/blog/tags/vite)[#Jest](/blog/tags/jest)[#React Testing Library](/blog/tags/react-testing-library)

## Share

[Facebook](https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fbuilding-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript "Share on Facebook")[Twitter](https://twitter.com/intent/tweet/?text=Building%20a%20Customizable%20Image%20Slider%20in%20React%20Using%20Hooks%2C%20SCSS%2C%20and%20TypeScript&url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fbuilding-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript "Share on Twitter")[LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fbuilding-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript&title=Building%20a%20Customizable%20Image%20Slider%20in%20React%20Using%20Hooks%2C%20SCSS%2C%20and%20TypeScript&summary=This%20article%20will%20guide%20you%20through%20the%20process%20of%20creating%20a%20React%20slider%20component%20using%20Hooks%2C%20SCSS%2C%20and%20TypeScript.%20By%20the%20end%20of%20this%20tutorial%2C%20you%20will%20have%20a%20functional%20and%20customizable%20slider%20that%20can%20be%20easily%20integrated%20into%20your%20project.&source=https://mkabumattar.com "Share on LinkedIn")[WhatsApp](https://wa.me/?text=Building%20a%20Customizable%20Image%20Slider%20in%20React%20Using%20Hooks%2C%20SCSS%2C%20and%20TypeScript%20https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fbuilding-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript "Share on WhatsApp")[Telegram](https://t.me/share/url?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fbuilding-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript&text=Building%20a%20Customizable%20Image%20Slider%20in%20React%20Using%20Hooks%2C%20SCSS%2C%20and%20TypeScript "Share on Telegram")[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fbuilding-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript&title=Building%20a%20Customizable%20Image%20Slider%20in%20React%20Using%20Hooks%2C%20SCSS%2C%20and%20TypeScript "Share on Reddit")[Hacker News](http://news.ycombinator.com/submitlink?u=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fbuilding-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript&t=Building%20a%20Customizable%20Image%20Slider%20in%20React%20Using%20Hooks%2C%20SCSS%2C%20and%20TypeScript "Share on Hacker News")[Pinterest](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fbuilding-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript&media=&description=This%20article%20will%20guide%20you%20through%20the%20process%20of%20creating%20a%20React%20slider%20component%20using%20Hooks%2C%20SCSS%2C%20and%20TypeScript.%20By%20the%20end%20of%20this%20tutorial%2C%20you%20will%20have%20a%20functional%20and%20customizable%20slider%20that%20can%20be%20easily%20integrated%20into%20your%20project. "Share on Pinterest")[Email](<mailto:?subject=Building%20a%20Customizable%20Image%20Slider%20in%20React%20Using%20Hooks%2C%20SCSS%2C%20and%20TypeScript&body=Check out this article: https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Fbuilding-a-customizable-image-slider-in-react-using-hooks-scss-and-typescript>)

## Comments

## You might also enjoy

More posts on similar topics

[![React With Redux Toolkit](/_astro/hero.DS6Oq_Cn_153Dmu.webp)](/blog/post/react-with-redux-toolkit)

## [React With Redux Toolkit](/blog/post/react-with-redux-toolkit)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [ReactJS](/blog/categories/reactjs)
-   [Redux](/blog/categories/redux)
-   [State Management](/blog/categories/state-management)
-   [Frontend Development](/blog/categories/frontend-development)

Prerequisites This post assumes that you have a basic understanding of React and Redux, and it helps if you have some experience with React Hooks like useReducer. Introduction Nowadays, we

[#ReactJS](/blog/tags/reactjs)[#Redux Toolkit](/blog/tags/redux-toolkit)[#State Management](/blog/tags/state-management)+5 tags

[read more](/blog/post/react-with-redux-toolkit)

[![React Context API for State Management](/_astro/hero.Dv7VXd7h_Z4sE61.webp)](/blog/post/react-context-api-state-management)

## [React Context API for State Management](/blog/post/react-context-api-state-management)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [ReactJS](/blog/categories/reactjs)
-   [State Management](/blog/categories/state-management)
-   [Frontend Development](/blog/categories/frontend-development)
-   [Next.js](/blog/categories/nextjs)
-   [JavaScript](/blog/categories/javascript)

Introduction Managing application state well can make or break a React project. React offers several options for state management, and the Context API is one of the most flexible. But what exactly

[#React Context API](/blog/tags/react-context-api)[#State Management](/blog/tags/state-management)[#Redux](/blog/tags/redux)+7 tags

[read more](/blog/post/react-context-api-state-management)

[![Setup Nextjs Tailwind CSS Styled Components with TypeScript](/_astro/hero.BP7GbA0g_O8bl1.webp)](/blog/post/setup-nextjs-tailwind-css-styled-components-with-typescript)

## [Setup Nextjs Tailwind CSS Styled Components with TypeScript](/blog/post/setup-nextjs-tailwind-css-styled-components-with-typescript)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Next.js](/blog/categories/nextjs)
-   [Tailwind CSS](/blog/categories/tailwind-css)
-   [Styled Components](/blog/categories/styled-components)
-   [TypeScript](/blog/categories/typescript)
-   [Frontend Development](/blog/categories/frontend-development)

Introduction In this post, we will set up Nextjs, Tailwind CSS and Styled Components with TypeScript, using the following tools:Nextjs Tailwind CSS Styled Components TypeScriptPrere

[#Next.js Setup](/blog/tags/nextjs-setup)[#Tailwind CSS Integration](/blog/tags/tailwind-css-integration)[#Styled Components with Next.js](/blog/tags/styled-components-with-nextjs)+4 tags

[read more](/blog/post/setup-nextjs-tailwind-css-styled-components-with-typescript)

[![TypeScript vs. JSDoc: Static Type Checking in JavaScript Compared](/_astro/hero.DqNoDaeT_2oupii.webp)](/blog/post/typescript-vs-jsdoc-exploring-the-pros-and-cons-of-static-type-checking-in-javascript)

## [TypeScript vs. JSDoc: Static Type Checking in JavaScript Compared](/blog/post/typescript-vs-jsdoc-exploring-the-pros-and-cons-of-static-type-checking-in-javascript)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [JavaScript](/blog/categories/javascript)
-   [TypeScript](/blog/categories/typescript)
-   [Static Typing](/blog/categories/static-typing)
-   [Development Tools](/blog/categories/development-tools)
-   [Code Quality](/blog/categories/code-quality)

TL;DRTypeScript and JSDoc are two tools for static type checking in JavaScript. TypeScript offers a full type system, advanced features, and strict type checking. JSDoc provides lightweight

[#TypeScript](/blog/tags/typescript)[#JSDoc](/blog/tags/jsdoc)[#Static Type Checking](/blog/tags/static-type-checking)+4 tags

[read more](/blog/post/typescript-vs-jsdoc-exploring-the-pros-and-cons-of-static-type-checking-in-javascript)

[![Run TypeScript Without Compiling](/_astro/hero.EI1J4T1U_ZTF3Kf.webp)](/blog/post/run-typescript-without-compiling)

## [Run TypeScript Without Compiling](/blog/post/run-typescript-without-compiling)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [TypeScript](/blog/categories/typescript)
-   [Node.js](/blog/categories/nodejs)
-   [JavaScript](/blog/categories/javascript)
-   [Development Tools](/blog/categories/development-tools)

Introduction In this post, I will show you how to run TypeScript without compiling it to JavaScript first. This is useful for debugging and testing. Set up a TypeScript project Step 1: cr

[#TypeScript](/blog/tags/typescript)[#Node.js](/blog/tags/nodejs)[#Ts node](/blog/tags/ts-node)+6 tags

[read more](/blog/post/run-typescript-without-compiling)

[![Setting up Node.js, Express, Prettier, ESLint, and Husky application with Babel and TypeScript - Part 1](/_astro/hero.DKzl3k6w_w3X8j.webp)](/blog/post/setting-up-node-js-express-prettier-eslint-and-husky-application-with-babel-and-typescript-part-1)

## [Setting up Node.js, Express, Prettier, ESLint, and Husky application with Babel and TypeScript - Part 1](/blog/post/setting-up-node-js-express-prettier-eslint-and-husky-application-with-babel-and-typescript-part-1)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Backend Development](/blog/categories/backend-development)
-   [Node.js](/blog/categories/nodejs)
-   [TypeScript](/blog/categories/typescript)
-   [Development Setup](/blog/categories/development-setup)
-   [JavaScript Tooling](/blog/categories/javascript-tooling)

Introduction All code from this tutorial as a complete package is available in this repos

[#Node.js](/blog/tags/nodejs)[#Express.js](/blog/tags/expressjs)[#TypeScript](/blog/tags/typescript)+9 tags

[read more](/blog/post/setting-up-node-js-express-prettier-eslint-and-husky-application-with-babel-and-typescript-part-1)

6 related posts
