In the previous lesson, we created a Product component to display the details of a product along with its image. In this lesson, we will extend the Product component to display a set of images as a carousel instead of a single image:
To achieve this, we will create a new component, Carousel, and then use it in the Product component. The Carousel component should display an image along with two arrow buttons. Additionally, it should update the displayed image when the user interacts with the arrow buttons.
Rather than constructing the entire component in one go, we will adopt an incremental approach by building the component as small and implementable layers.
Let’s get started by creating a Carousel.jsx file for this component:
As the first step, we will display an image along with two arrow buttons. We will use the Button component from neeto-ui along with Left and Right icons from neeto-icons for previous and next buttons. For the Carousel component to be reusable, we will add an imageUrls prop to receive a set of URLs as an array of strings. Initially, we can display the first image from the imageUrls we have passed.
By keeping the principles of web accessibility in mind, we will add the alt attribute to the img tag to display an alternate text if the image doesn't load. This text will also be used by the screen readers to read the content loud for the visually impaired readers. We will add a title prop to the Carousel component for the same:
We can use this component inside our Product component to display a Carousel. For that, we need a set of images. We can use some dummy images from the internet for the same:
Let us create a constant array of these URLs. At Bigbinary, we follow the convention of naming constants in UPPER_SNAKE_CASE and grouping them within a file constants.js. Following this convention, let's create a constants.js file within src/components and place the above URLs into an array:
Now, we can replace the img tag in the Product component with the Carousel component:
Our next goal is to add interactivity to the Carousel component by enabling users to navigate between product images by clicking the arrow buttons.
Let's break down this requirement further:
-
Currently, we are passing the URL at the 0th index of the imageUrls array as the src prop. Instead of hardcoding 0, we need to make the array index dynamic. Whenever this index changes, we need to re-render the image with the new src URL. To achieve this, we will use a React feature called State.
-
Next, we need to increment or decrement the index when the user clicks on the next or previous buttons. For that, we need to listen to button clicks. We will learn the concept of Event handlers in React to achieve this.
State in React
To store dynamic values associated with a component, we can use the concept of state from React. React state stores a value, keeps track of its changes, and updates the user interface accordingly.
Most of React's powerful features, including states, are made accessible through special functions known as hooks. One such essential hook is useState, which enables us to create states. It accepts an initial value and returns an array containing the current value of the state variable and a function to update this state variable. Here is the syntax for using the useState function:
Here we have destructured the array returned by the useState function to the variables state and setState. The first destructured variable is the name of the item we're tracking. The second variable prefixes that name with set, signifying that it's a function that can be invoked to update the tracked item. This function is sometimes referred to as a setter function since it sets the new value for the state variable.
We can name these variables whatever we want. However, it is a convention followed across the React community to name the setter function as set followed by the state variable name.
For example, we can name the state variable to store the current image index in our Carousel component as currentIndex and the setter function as setCurrentIndex. Since we need to display the first image from the imageUrls array on page load, we will set the initial value of currentIndex as 0:
Let's add the state variable currentIndex to our Carousel component using the useState hook:
There are a couple of rules that we need to follow while using hooks:
- Hooks should only be called within React components. It is not valid to call hooks outside components.
- Hooks should be called at the top level of the React component, before any early returns. This means that hooks cannot be called inside loops, conditions, or nested functions.
Event handling
You might be familiar with handling events in JavaScript. In JavaScript, browser events are handled by attaching event listeners to DOM elements:
HTML offers another way to handle events using event attributes:
React follows a pattern similar to HTML for event handling, but there are two key distinctions:
- React event handler props are named using camelCase instead of lowercase like,
onClick, onChange, onKeyDown, etc.
- With JSX, we pass the function reference as the event handler instead of a string.
Here is an example of handling an event in React:
For onClick, onChange, setter functions, and similar event handlers, it is a common convention to name the functions with a handle prefix, such as handleClick, handleChange, etc.
Let's jump into adding event handlers for the buttons in the Carousel component.
We need to increment the currentIndex when the user clicks the right arrow button and decrement it when clicking the left arrow. To ensure that the value of currentIndex stays within the array index range, we should take the modulus with the array length. We can utilize the setCurrentIndex function to update the currentIndex:
Go ahead and open your application to confirm the changes. You should now be able to navigate between the product images:
Let's reinforce what we have learned so far by adding an indicator to navigate between images:
Following are the steps to accomplish the above requirement:
- Display a set of dots indicating the number of images in the
imageUrls.
- Clicking each dot should update the
currentIndex. For example, clicking the 1st dot should update the currentIndex to 0, clicking the 2nd dot should update the currentIndex to 1 and so on.
- Based on the
currentIndex, change the color of the dot corresponding to the current image to black.
Before jumping straight into the solution, try to solve this yourself, with the knowledge about state and events handling.
Solution
Let’s see how we can add the dot indicator to our Carousel component.
- First, we will map over the
imageUrls array to display a set of dots. Note that here, we have passed the index as the key. In this case, it is okay to pass the index as key since the imageUrls is a constant array that won't ever change.
- Next, we will add an
onClick handler for each dot. We can pass the index of the corresponding dot as a parameter to the setCurrentIndex function. Here, as we need to pass the index parameter to the event handler, we need to define the event handler as an inline function.
- Finally, to highlight the position of the current image, we'll set a black background for the selected dot. We can achieve this by comparing the state variable
currentIndex with the index of each dot and applying the class name neeto-ui-bg-black conditionally:
However, we can avoid the hassle of writing the conditions as above by leveraging the classNames package to apply classnames conditionally. This package provides a function called classNames that facilitates the conditional joining of class names.
To install classnames, run the following command:
With the classNames function, we pass the classnames to include and the corresponding conditions as an object. In addition, the class names that should always be present are passed as a string.
Here is what the code will look like with the classNames package. Much better, right?
Hooray 🎉 ! We have learned the concepts of state and event handling in React and implemented a simple Carousel component.
Let's commit the new changes:
You can verify the changes here.