Type something to search...
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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
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.

src/components/Icons/RightArrowIcon.tsx
import React from 'react';
// type
export type Props = {
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>
);
};
export 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 main slider component. We will start by importing the RightArrowIcon component that we created in the previous step.

src/components/Slider/Slide.tsx
import React, {useRef, useEffect, MouseEvent} from 'react';
// icons
import RightArrowIcon from '../Icons/RightArrowIcon';
// types
export type Tag = {
name: string;
};
export type Link = {
name: string;
url: string;
};
export type SlideData = {
index: number;
src: string;
headline: string;
direction?: string;
tags?: Tag[];
links?: Link[];
};
export type SlideProps = {
slide: SlideData;
index: number;
current: number;
handleSlideClick: (e: MouseEvent<HTMLDivElement>) => void;
};
const Slide = ({slide, index, current, handleSlideClick}: SlideProps) => {
const slideRef = useRef<HTMLDivElement>(null);
const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
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<HTMLDivElement>) => {
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'}>
<RightArrowIcon />
</span>
</a>
))}
</div>
</div>
</div>
</div>
);
};
export default Slide;

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.

src/components/Slider/SliderControl.tsx
import React, {MouseEvent} from 'react';
// types
export type SliderControlProps = {
type: string;
title: string;
handleClick: (e: MouseEvent<HTMLButtonElement>) => 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>
);
};
export 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
import React, {useState, MouseEvent} from 'react';
import Slide, {SlideData} from './Slide';
import SliderControl from './SliderControl';
export type SliderProps = {
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<HTMLDivElement>) => {
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) => (
<Slide
key={index}
index={index}
slide={slide}
current={current}
handleSlideClick={handleSlideClick}
/>
))}
</div>
</div>
<div className={'slider__controls'}>
<SliderControl
type={'previous'}
title={'Go to previous slide'}
handleClick={handlePreviousClick}
/>
<SliderControl
type={'next'}
title={'Go to next slide'}
handleClick={handleNextClick}
/>
</div>
</>
);
};
export default Slider;

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.

src/components/Slider/index.tsx
import React from 'react';
import Slider from './Slider';
import {SlideData} from './Slide';
import './style.scss';
const SliderComponent = ({
slides,
heading,
}: {
slides: SlideData[];
heading: string;
}) => {
return (
<div>
<Slider slides={slides} heading={heading} />
</div>
);
};
export default SliderComponent;

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.

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:

App.tsx
import React from 'react';
import './style.scss';
// Import the slider component
import Slider from './components/Slider';
// Import the slider data
import sliderData from './data/slider.data.json';
// Import the slide data type
import {SlideData} from './components/Slider/Slide';
const App = () => {
return (
<div className="app">
<Slider
slides={sliderData.slides as SlideData[]}
heading={sliderData.heading}
/>
</div>
);
};
export default App;

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

Terminal window
npm run dev

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

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

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:

src/components/Icons/RightArrowIcon.test.tsx
import React from 'react';
import '@testing-library/jest-dom';
import {render} from '@testing-library/react';
import RightArrowIcon from './RightArrowIcon';
describe('RightArrowIcon', () => {
test('renders', () => {
const {container} = render(<RightArrowIcon />);
expect(container.firstChild).toBeInTheDocument();
});
test('has correct fill', () => {
const {container} = render(<RightArrowIcon fill="red" />);
expect(container.firstChild).toHaveAttribute('fill', 'red');
});
test('has correct size', () => {
const {container} = render(<RightArrowIcon size="24" />);
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:

src/components/SliderControl.test.tsx
import React from 'react';
import '@testing-library/jest-dom';
import {render, fireEvent} from '@testing-library/react';
import SliderControl from './SliderControl';
describe('SliderControl component', () => {
it('should render the correct title and type', () => {
const handleClick = jest.fn();
const {getByTitle} = render(
<SliderControl
type="prev"
title="Previous slide"
handleClick={handleClick}
/>,
);
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(
<SliderControl
type="next"
title="Next slide"
handleClick={handleClick}
/>,
);
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:

src/components/Slide.test.tsx
import React from 'react';
import '@testing-library/jest-dom';
import {render, fireEvent} from '@testing-library/react';
import Slide, {SlideData} from './Slide';
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(
<Slide
slide={slideData}
index={0}
current={0}
handleSlideClick={jest.fn()}
/>,
);
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(
<Slide
slide={slideData}
index={0}
current={0}
handleSlideClick={jest.fn()}
/>,
);
expect(container.firstChild).toHaveClass('slide slide--current');
});
test('adds slide--next class when current prop is 1 less than index prop', () => {
const {container} = render(
<Slide
slide={slideData}
index={1}
current={0}
handleSlideClick={jest.fn()}
/>,
);
expect(container.firstChild).toHaveClass('slide slide--next');
});
test('adds slide--previous class when current prop is 1 more than index prop', () => {
const {container} = render(
<Slide
slide={slideData}
index={0}
current={1}
handleSlideClick={jest.fn()}
/>,
);
expect(container.firstChild).toHaveClass('slide slide--previous');
});
test('calls handleSlideClick function when clicked', () => {
const mockHandleSlideClick = jest.fn();
const {container} = render(
<Slide
slide={slideData}
index={0}
current={0}
handleSlideClick={mockHandleSlideClick}
/>,
);
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:

src/components/Slider.test.tsx
import React from 'react';
import '@testing-library/jest-dom';
import {render, fireEvent} from '@testing-library/react';
import Slider from './Slider';
import {SlideData} from './Slide';
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(
<Slider slides={slides} heading={'Test Slider'} />,
);
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(
<Slider slides={slides} heading={'Test Slider'} />,
);
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(
<Slider slides={slides} heading={'Test Slider'} />,
);
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(
<Slider slides={slides} heading={'Test Slider'} />,
);
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:

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

Now, we will run the tests:

Terminal window
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

Live Demo

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

LIVE DEMO

Related Posts

Check out some of our other posts

Setup Nextjs Tailwind CSS Styled Components with TypeScript

Setup Nextjs Tailwind CSS Styled Components with TypeScript

Introduction In this post, we will setup Nextjs Tailwind CSS Styled Components with TypeScript, and we will use the following tools:Nextjs Tailwind CSS Styled Components TypeScript

read more
React With Redux Toolkit

React With Redux Toolkit

Prerequisites This post assumes that you have a basic understanding of React and Redux. and it will be better if you have some experience with React Hooks like useReducer. Introduction Now

read more
Run TypeScript Without Compiling

Run TypeScript Without Compiling

Introduction In this post, I will show you how to run TypeScript without compiling it to JavaScript. This is useful for debugging and testing. In this post, I will show you how to do it. Setu

read more
Introduction to Spring Boot Framework

Introduction to Spring Boot Framework

Introduction For creating web apps and microservices, many developers utilize the Spring Boot framework. The fact that it is built on top of the Spring Framework and offers a number of advantages

read more
RESTful API vs. GraphQL: Which API is the Right Choice for Your Project?

RESTful API vs. GraphQL: Which API is the Right Choice for Your Project?

TL;DR When deciding between RESTful and GraphQL APIs for a data analysis and display application, it is important to consider the advantages and disadvantages of each. RESTful APIs have been arou

read more
TypeScript vs. JSDoc: Exploring the Pros and Cons of Static Type Checking in JavaScript

TypeScript vs. JSDoc: Exploring the Pros and Cons of Static Type Checking in JavaScript

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

read more